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 6a06996ecb2..ac533e6b679 100644 --- a/Mage.Client/src/main/java/mage/client/combat/CombatManager.java +++ b/Mage.Client/src/main/java/mage/client/combat/CombatManager.java @@ -83,7 +83,7 @@ public enum CombatManager { UUID defenderId = group.getDefenderId(); if (defenderId != null) { // if attacker was blocked then use another arrow color - Color attackColor = group.getBlockers().isEmpty() ? ARROW_COLOR_ATTACKER : ARROW_COLOR_BLOCKED_ATTACKER; + Color attackColor = group.isBlocked() ? ARROW_COLOR_BLOCKED_ATTACKER : ARROW_COLOR_ATTACKER; parentPoint = getParentPoint(attackerCard); PlayAreaPanel p = MageFrame.getGamePlayers(gameId).get(defenderId); if (p != null) { diff --git a/Mage.Client/src/main/java/mage/client/dialog/TestCardRenderDialog.java b/Mage.Client/src/main/java/mage/client/dialog/TestCardRenderDialog.java index 7cfc0c66c25..51eaabe44bf 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/TestCardRenderDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/TestCardRenderDialog.java @@ -326,6 +326,13 @@ public class TestCardRenderDialog extends MageDialog { possibleTargets.add(playerYou.getId()); } + // chosen target + Set chosenTargets = null; + if (false) { // TODO: add GUI's checkbox for checkPlayerAsChosen + chosenTargets = new LinkedHashSet<>(); + chosenTargets.add(playerYou.getId()); + } + // player's panel if (this.player == null) { // create new panel @@ -345,7 +352,7 @@ public class TestCardRenderDialog extends MageDialog { .findFirst() .orElse(null); this.player.init(this.game.getId(), playerYou.getId(), isMe, this.bigCard, 0); - this.player.update(gameView, currentPlayerView, possibleTargets); + this.player.update(gameView, currentPlayerView, possibleTargets, chosenTargets); this.player.sizePlayerPanel(smallMode); // update CARDS diff --git a/Mage.Client/src/main/java/mage/client/game/GamePanel.java b/Mage.Client/src/main/java/mage/client/game/GamePanel.java index 987db75da17..90074e9331b 100644 --- a/Mage.Client/src/main/java/mage/client/game/GamePanel.java +++ b/Mage.Client/src/main/java/mage/client/game/GamePanel.java @@ -218,6 +218,22 @@ public final class GamePanel extends javax.swing.JPanel { }); } + public List getPossibleTargets() { + if (options != null && options.containsKey("possibleTargets")) { + return (List) options.get("possibleTargets"); + } else { + return Collections.emptyList(); + } + } + + public Set getChosenTargets() { + if (options != null && options.containsKey("chosenTargets")) { + return new HashSet<>((List) options.get("chosenTargets")); + } else { + return Collections.emptySet(); + } + } + public CardView findCard(UUID id) { return this.allCardsIndex.getOrDefault(id, null); } @@ -658,7 +674,7 @@ public final class GamePanel extends javax.swing.JPanel { // see test render dialog for refresh commands order playPanel.getPlayerPanel().fullRefresh(GUISizeHelper.playerPanelGuiScale); playPanel.init(player, bigCard, gameId, player.getPriorityTimeLeftSecs()); - playPanel.update(lastGameData.game, player, lastGameData.targets); + playPanel.update(lastGameData.game, player, lastGameData.targets, lastGameData.getChosenTargets()); playPanel.getPlayerPanel().sizePlayerPanel(false); } }); @@ -1186,7 +1202,7 @@ public final class GamePanel extends javax.swing.JPanel { } } } - players.get(player.getPlayerId()).update(lastGameData.game, player, lastGameData.targets); + players.get(player.getPlayerId()).update(lastGameData.game, player, lastGameData.targets, lastGameData.getChosenTargets()); if (player.getPlayerId().equals(playerId)) { skipButtons.updateFromPlayer(player); } @@ -1802,12 +1818,7 @@ public final class GamePanel extends javax.swing.JPanel { needZone = (Zone) lastGameData.options.get("targetZone"); } - List needChosen; - if (lastGameData.options != null && lastGameData.options.containsKey("chosenTargets")) { - needChosen = (List) lastGameData.options.get("chosenTargets"); - } else { - needChosen = new ArrayList<>(); - } + Set needChosen = lastGameData.getChosenTargets(); Set needSelectable; if (lastGameData.targets != null) { @@ -2037,7 +2048,7 @@ public final class GamePanel extends javax.swing.JPanel { private void prepareSelectableWindows( Collection windows, Set needSelectable, - List needChosen, + Set needChosen, PlayableObjectsList needPlayable ) { // lookAt or reveals windows clean up on next priority, so users can see dialogs, but xmage can't restore it diff --git a/Mage.Client/src/main/java/mage/client/game/PlayAreaPanel.java b/Mage.Client/src/main/java/mage/client/game/PlayAreaPanel.java index a3209317eef..162a821703a 100644 --- a/Mage.Client/src/main/java/mage/client/game/PlayAreaPanel.java +++ b/Mage.Client/src/main/java/mage/client/game/PlayAreaPanel.java @@ -57,7 +57,7 @@ public class PlayAreaPanel extends javax.swing.JPanel { // data init init(player, bigCard, gameId, priorityTime); - update(null, player, null); + update(null, player, null, null); playerPanel.sizePlayerPanel(isSmallMode()); // init popup menu (must run after data init) @@ -510,8 +510,8 @@ public class PlayAreaPanel extends javax.swing.JPanel { this.isMe = player.getControlled(); } - public final void update(GameView game, PlayerView player, Set possibleTargets) { - this.playerPanel.update(game, player, possibleTargets); + public final void update(GameView game, PlayerView player, Set possibleTargets, Set chosenTargets) { + this.playerPanel.update(game, player, possibleTargets, chosenTargets); this.battlefieldPanel.update(player.getBattlefield()); if (this.allowViewHandCardsMenuItem != null) { this.allowViewHandCardsMenuItem.setSelected(player.getUserData().isAllowRequestHandToAll()); diff --git a/Mage.Client/src/main/java/mage/client/game/PlayerPanelExt.java b/Mage.Client/src/main/java/mage/client/game/PlayerPanelExt.java index 3ba80d606d9..3fb8f428b08 100644 --- a/Mage.Client/src/main/java/mage/client/game/PlayerPanelExt.java +++ b/Mage.Client/src/main/java/mage/client/game/PlayerPanelExt.java @@ -247,7 +247,7 @@ public class PlayerPanelExt extends javax.swing.JPanel { .orElse(0); } - public void update(GameView game, PlayerView player, Set possibleTargets) { + public void update(GameView game, PlayerView player, Set possibleTargets, Set chosenTargets) { this.player = player; int pastLife = player.getLife(); if (playerLives != null) { @@ -427,6 +427,12 @@ public class PlayerPanelExt extends javax.swing.JPanel { this.btnPlayer.setBorder(YELLOW_BORDER); } + // selected targeting (draw as priority) + if (chosenTargets != null && chosenTargets.contains(this.playerId)) { + this.avatar.setBorder(GREEN_BORDER); // TODO: use diff green color for chosen targeting and current priority? + this.btnPlayer.setBorder(GREEN_BORDER); + } + update(player.getManaPool()); } diff --git a/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java b/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java index 30e88a12e1e..3fb8dcea16f 100644 --- a/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java +++ b/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java @@ -10,6 +10,7 @@ import mage.client.draft.DraftPanel; import mage.client.game.GamePanel; import mage.client.plugins.impl.Plugins; import mage.client.util.DeckUtil; +import mage.client.util.GUISizeHelper; import mage.client.util.IgnoreList; import mage.client.util.audio.AudioManager; import mage.client.util.object.SaveObjectUtil; @@ -22,9 +23,12 @@ import mage.util.DebugUtil; import mage.view.*; import mage.view.ChatMessage.MessageType; import org.apache.log4j.Logger; +import org.mage.card.arcane.ManaSymbols; import javax.swing.*; +import java.awt.*; import java.awt.event.KeyEvent; +import java.util.List; import java.util.*; /** @@ -203,11 +207,7 @@ public class CallbackClientImpl implements CallbackClient { case SERVER_MESSAGE: { if (callback.getData() != null) { ChatMessage message = (ChatMessage) callback.getData(); - if (message.getColor() == ChatMessage.MessageColor.RED) { - JOptionPane.showMessageDialog(null, message.getMessage(), "Server message", JOptionPane.WARNING_MESSAGE); - } else { - JOptionPane.showMessageDialog(null, message.getMessage(), "Server message", JOptionPane.INFORMATION_MESSAGE); - } + showMessageDialog(null, message.getMessage(), "Server message"); } break; } @@ -401,7 +401,7 @@ public class CallbackClientImpl implements CallbackClient { case SHOW_USERMESSAGE: { List messageData = (List) callback.getData(); if (messageData.size() == 2) { - JOptionPane.showMessageDialog(null, messageData.get(1), messageData.get(0), JOptionPane.WARNING_MESSAGE); + showMessageDialog(null, messageData.get(1), messageData.get(0)); } break; } @@ -420,8 +420,7 @@ public class CallbackClientImpl implements CallbackClient { GameClientMessage message = (GameClientMessage) callback.getData(); GamePanel panel = MageFrame.getGame(callback.getObjectId()); if (panel != null) { - JOptionPane.showMessageDialog(panel, message.getMessage(), "Game message", - JOptionPane.INFORMATION_MESSAGE); + showMessageDialog(panel, message.getMessage(), "Game message"); } break; } @@ -510,6 +509,18 @@ public class CallbackClientImpl implements CallbackClient { }); } + /** + * Show modal message box, so try to use it only for critical errors or global message. As less as possible. + */ + private void showMessageDialog(Component parentComponent, String message, String title) { + // convert to html + // message - supported + // title - not supported + message = ManaSymbols.replaceSymbolsWithHTML(message, ManaSymbols.Type.DIALOG); + message = GUISizeHelper.textToHtmlWithSize(message, GUISizeHelper.dialogFont); + JOptionPane.showMessageDialog(parentComponent, message, title, JOptionPane.INFORMATION_MESSAGE); + } + private ActionData appendJsonEvent(String name, UUID gameId, Object value) { Session session = SessionHandler.getSession(); if (session.isJsonLogActive()) { diff --git a/Mage.Client/src/main/java/mage/client/util/GUISizeHelper.java b/Mage.Client/src/main/java/mage/client/util/GUISizeHelper.java index d7559ecce12..d0b1e9f06df 100644 --- a/Mage.Client/src/main/java/mage/client/util/GUISizeHelper.java +++ b/Mage.Client/src/main/java/mage/client/util/GUISizeHelper.java @@ -6,6 +6,7 @@ import mage.client.util.gui.GuiDisplayUtil; import org.mage.card.arcane.CardRenderer; import javax.swing.*; +import javax.swing.plaf.FontUIResource; import java.awt.*; import java.lang.reflect.Field; import java.util.Locale; @@ -108,7 +109,7 @@ public final class GUISizeHelper { // app - frame/window title // nimbus's LaF limited to static title size, so font can't be too big (related code in SynthInternalFrameTitlePane, BasicInternalFrameTitlePane) - UIManager.put("InternalFrame.titleFont", dialogFont.deriveFont(Font.BOLD, Math.min(17, 0.8f * dialogFont.getSize()))); + UIManager.put("InternalFrame.titleFont", new FontUIResource(dialogFont.deriveFont(Font.BOLD, Math.min(17, 0.8f * dialogFont.getSize())))); // app - tables tableFont = new java.awt.Font("Arial", 0, dialogFontSize); @@ -150,7 +151,11 @@ public final class GUISizeHelper { cardTooltipLargeImageHeight = 30 * tooltipFontSize; cardTooltipLargeTextWidth = Math.max(150, 20 * tooltipFontSize - 50); cardTooltipLargeTextHeight = Math.max(100, 12 * tooltipFontSize - 20); - UIManager.put("ToolTip.font", cardTooltipFont); + UIManager.put("ToolTip.font", new FontUIResource(cardTooltipFont)); + + // app - information boxes (only title, text controls by content) + // TODO: doesn't work + //UIManager.put("OptionPane.titleFont", new FontUIResource(dialogFont.deriveFont(Font.BOLD, Math.min(17, 1.3f * dialogFont.getSize())))); // game - player panel playerPanelGuiScale = (float) (PreferencesDialog.getCachedValue(PreferencesDialog.KEY_GUI_PLAYER_PANEL_SIZE, 14) / 14.0); 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 49d6b83feda..7066f6ae128 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 @@ -474,7 +474,7 @@ public final class GuiDisplayUtil { public static void refreshThemeSettings() { // apply Nimbus's look and fill // possible settings: - // https://docs.oracle.com/en%2Fjava%2Fjavase%2F17%2Fdocs%2Fapi%2F%2F/java.desktop/javax/swing/plaf/nimbus/doc-files/properties.html + // https://docs.oracle.com/en/java/javase/23/docs/api/java.desktop/javax/swing/plaf/nimbus/doc-files/properties.html // enable nimbus try { diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/RetroCardRenderer.java b/Mage.Client/src/main/java/org/mage/card/arcane/RetroCardRenderer.java index 961b6a43ce7..396ebf147cb 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/RetroCardRenderer.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/RetroCardRenderer.java @@ -105,21 +105,25 @@ public class RetroCardRenderer extends CardRenderer { // = cardWidth - 2 x totalContentInset protected int contentWidth; - // Width of art / text box + // dimensions of art / text box protected int innerContentWidth; - // X position of inside content - private int innerContentStart; - + protected int artHeight; // How tall the name / type lines and P/T box are - protected static final float BOX_HEIGHT_FRAC = 0.065f; // x cardHeight - protected static final int BOX_HEIGHT_MIN = 16; + protected static final float BOX_HEIGHT_FRAC = 0.051f; // x cardHeight + protected static final float PT_BOX_HEIGHT_FRAC = 0.065f; // x cardHeight + protected static final int BOX_HEIGHT_MIN = 8; protected int boxHeight; + protected int ptBoxHeight; // How far down the card is the type line placed? - protected static final float TYPE_LINE_Y_FRAC = 0.52f; // x cardHeight + protected static final float TYPE_LINE_Y_FRAC = 0.525f; // x cardHeight protected int typeLineY; + // The left and right frame inset + protected static final float INSET_WIDTH_FRAC = .055f; + protected int insetWidth; + // Possible sizes of rules text font protected static final int[] RULES_TEXT_FONT_SIZES = {24, 18, 15, 12, 9}; @@ -156,14 +160,18 @@ public class RetroCardRenderer extends CardRenderer { borderWidth = (int) Math.max( BORDER_WIDTH_MIN, - 0.042 * cardWidth); + 0.048 * cardWidth); frameInset = (int) Math.max( BORDER_WIDTH_MIN, 0.012 * cardWidth); + insetWidth = (int) Math.max( + BORDER_WIDTH_MIN, + INSET_WIDTH_FRAC * cardWidth); + // Content inset, just equal to border width - contentInset = borderWidth - frameInset; + contentInset = borderWidth + insetWidth; // Total content inset helper totalContentInset = borderWidth + contentInset; @@ -175,14 +183,20 @@ public class RetroCardRenderer extends CardRenderer { boxHeight = (int) Math.max( BOX_HEIGHT_MIN, BOX_HEIGHT_FRAC * cardHeight); - - // Art / text box size - innerContentWidth = (int) (cardWidth * 0.81f); - innerContentStart = (int) (cardWidth * 0.095f); + ptBoxHeight = (int) Math.max( + BOX_HEIGHT_MIN * 2, + PT_BOX_HEIGHT_FRAC * cardHeight); // Type line at typeLineY = (int) (TYPE_LINE_Y_FRAC * cardHeight); + // Art / text box size + innerContentWidth = (int) (cardWidth * .8f); + if (innerContentWidth < 160) { + innerContentWidth += 2; + } + artHeight = typeLineY - (borderWidth + boxHeight); + // Box text height boxTextHeight = getTextHeightForBoxHeight(boxHeight); boxTextOffset = (boxHeight - boxTextHeight) / 2; @@ -190,8 +204,8 @@ public class RetroCardRenderer extends CardRenderer { boxTextFontNarrow = new Font("Arial Narrow", Font.PLAIN, boxTextHeight); // Box text height - ptTextHeight = getPTTextHeightForLineHeight(boxHeight); - ptTextOffset = (boxHeight - ptTextHeight) / 2; + ptTextHeight = getPTTextHeightForLineHeight(ptBoxHeight); + ptTextOffset = (ptBoxHeight - ptTextHeight) / 2; ptTextFont = new Font("Arial", Font.BOLD, ptTextHeight); // Inset Frame Colors @@ -251,7 +265,7 @@ public class RetroCardRenderer extends CardRenderer { protected void drawArt(Graphics2D g) { if (artImage != null) { - boolean shouldPreserveAspect = false; + boolean shouldPreserveAspect = true; Rectangle2D sourceRect = ArtRect.RETRO.rect; if (cardView.getFrameStyle() != FrameStyle.RETRO) { sourceRect = new Rectangle2D.Double(sourceRect.getX(), sourceRect.getY() + .01, sourceRect.getWidth(), sourceRect.getHeight()); @@ -266,8 +280,8 @@ public class RetroCardRenderer extends CardRenderer { // Normal drawing of art from a source part of the card frame into the rect drawArtIntoRect(g, - innerContentStart + frameInset, innerContentStart + frameInset * 2, - innerContentWidth - frameInset * 2, typeLineY - borderWidth * 2 - frameInset, + contentInset + frameInset, borderWidth + boxHeight + frameInset, + innerContentWidth - frameInset * 2, artHeight - frameInset * 2, sourceRect, shouldPreserveAspect); } @@ -291,28 +305,28 @@ public class RetroCardRenderer extends CardRenderer { // Draw the textbox fill drawTextboxBackground(g, textboxPaint, frameColors, borderPaint, isOriginalDualLand()); - drawInsetFrame(g, innerContentStart, innerContentStart + frameInset, - innerContentWidth, typeLineY - borderWidth * 2 + frameInset, borderPaint, cardView.getCardTypes().contains(CardType.LAND)); + drawInsetFrame(g, contentInset, borderWidth + boxHeight, + innerContentWidth, artHeight, borderPaint, cardView.getCardTypes().contains(CardType.LAND)); drawTypeLine(g, attribs, getCardTypeLine(), - innerContentStart, typeLineY + frameInset, - innerContentWidth, boxHeight + frameInset); + contentInset, typeLineY, + innerContentWidth, boxHeight); // Draw the transform circle int nameOffset = drawTransformationCircle(g, attribs, borderPaint); // Draw the name line drawNameLine(g, attribs, cardView.getDisplayName(), manaCostString, - innerContentStart + nameOffset, totalContentInset / 2 - frameInset, - contentWidth - nameOffset - borderWidth); + contentInset + nameOffset, borderWidth, + innerContentWidth); // Draw the textbox rules drawRulesText(g, textboxKeywords, textboxRules, - innerContentStart + 2, typeLineY + boxHeight + 2, + contentInset + 2, typeLineY + boxHeight + 2, innerContentWidth - 4, (int) ((cardHeight - borderWidth * 2) * 0.32f)); // Draw the bottom right stuff - drawBottomRight(g, borderPaint, boxColor); + drawBottomRight(g, attribs, borderPaint, boxColor); } private void drawInsetFrame(Graphics2D g2, int x, int y, int width, int height, Paint borderPaint, boolean isLand) { @@ -390,7 +404,7 @@ public class RetroCardRenderer extends CardRenderer { private void drawTextboxBackground(Graphics2D g, Paint textboxPaint, ObjectColor frameColors, Paint borderPaint, boolean isOriginalDual) { g.setPaint(textboxPaint); - int x = innerContentStart; + int x = contentInset; int backgroundHeight = (int) ((cardHeight - borderWidth * 2) * 0.33f); if (cardView.getCardTypes().contains(CardType.LAND)) { @@ -526,12 +540,14 @@ public class RetroCardRenderer extends CardRenderer { } // Draw the P/T and/or Loyalty boxes - protected void drawBottomRight(Graphics2D g, Paint borderPaint, Color fill) { + protected void drawBottomRight(Graphics2D g, CardPanelAttributes attribs, Paint borderPaint, Color fill) { // No bottom right for abilities if (cardView.isAbility()) { return; } + int contentInset = borderWidth - frameInset; + // Where to start drawing the things int curY = cardHeight - (int) (0.03f * cardHeight); @@ -559,15 +575,16 @@ public class RetroCardRenderer extends CardRenderer { // Draw PT box CardRendererUtils.drawRoundedBox(g, - x, curY - boxHeight, - partBoxWidth, boxHeight, + x, curY - ptBoxHeight, + partBoxWidth, ptBoxHeight, contentInset, borderPaint, isVehicle ? BOX_VEHICLE : fill); // Draw text Color defaultTextColor = Color.black; - boolean defaultTextLight = true; + boolean defaultTextLight = cardView.getColor().isMulticolored() || cardView.getColor().equals(ObjectColor.RED) + || cardView.getColor().equals(ObjectColor.COLORLESS); g.setFont(ptTextFont); // real PT info @@ -592,7 +609,7 @@ public class RetroCardRenderer extends CardRenderer { g.setColor(defaultTextColor); // Advance - curY -= boxHeight; + curY -= ptBoxHeight; } // Is it a walker? (But don't draw the box if it's a non-permanent view @@ -696,14 +713,14 @@ public class RetroCardRenderer extends CardRenderer { // does it have damage on it? if ((cardView instanceof PermanentView) && ((PermanentView) cardView).getDamage() > 0) { int x = cardWidth - partBoxWidth - borderWidth; - int y = curY - boxHeight; + int y = curY - ptBoxHeight; String damage = String.valueOf(((PermanentView) cardView).getDamage()); g.setFont(ptTextFont); int txWidth = g.getFontMetrics().stringWidth(damage); g.setColor(Color.red); - g.fillRect(x, y, partBoxWidth, boxHeight); + g.fillRect(x, y, partBoxWidth, ptBoxHeight); g.setColor(Color.white); - g.drawRect(x, y, partBoxWidth, boxHeight); + g.drawRect(x, y, partBoxWidth, ptBoxHeight); g.drawString(damage, x + (partBoxWidth - txWidth) / 2, curY - 1); } } @@ -998,11 +1015,7 @@ public class RetroCardRenderer extends CardRenderer { // Get the text height for a given box height protected static int getTextHeightForBoxHeight(int h) { - if (h < 15) { - return h - 3; - } else { - return (int) Math.ceil(.6 * h); - } + return Math.max(10, (int) Math.ceil(.95 * h)); } protected static int getPTTextHeightForLineHeight(int h) { @@ -1056,8 +1069,6 @@ public class RetroCardRenderer extends CardRenderer { protected static BufferedImage getBackgroundTexture(ObjectColor colors, Collection types) { if (types.contains(CardType.LAND)) { return BG_IMG_LAND; - } else if (types.contains(CardType.ARTIFACT)) { - return BG_IMG_ARTIFACT; } else if (colors.isMulticolored()) { return BG_IMG_GOLD; } else if (colors.isWhite()) { @@ -1070,6 +1081,8 @@ public class RetroCardRenderer extends CardRenderer { return BG_IMG_RED; } else if (colors.isGreen()) { return BG_IMG_GREEN; + } else if (types.contains(CardType.ARTIFACT)) { + return BG_IMG_ARTIFACT; } else { // Colorless return BG_IMG_COLORLESS; diff --git a/Mage.Common/src/main/java/mage/utils/SystemUtil.java b/Mage.Common/src/main/java/mage/utils/SystemUtil.java index 7d314cdf4ec..408e0b0de29 100644 --- a/Mage.Common/src/main/java/mage/utils/SystemUtil.java +++ b/Mage.Common/src/main/java/mage/utils/SystemUtil.java @@ -28,6 +28,7 @@ import mage.target.common.TargetOpponent; import mage.util.CardUtil; import mage.util.MultiAmountMessage; import mage.util.RandomUtil; +import mage.utils.testers.TestableDialogsRunner; import java.io.File; import java.lang.reflect.Constructor; @@ -69,6 +70,7 @@ public final class SystemUtil { private static final String COMMAND_LANDS_ADD_TO_BATTLEFIELD = "@lands add"; private static final String COMMAND_UNDER_CONTROL_TAKE = "@under control take"; private static final String COMMAND_UNDER_CONTROL_GIVE = "@under control give"; + private static final String COMMAND_SHOW_TEST_DIALOGS = "@show dialog"; private static final String COMMAND_MANA_ADD = "@mana add"; // TODO: not implemented private static final String COMMAND_RUN_CUSTOM_CODE = "@run custom code"; // TODO: not implemented private static final String COMMAND_SHOW_OPPONENT_HAND = "@show opponent hand"; @@ -76,6 +78,7 @@ public final class SystemUtil { private static final String COMMAND_SHOW_MY_HAND = "@show my hand"; private static final String COMMAND_SHOW_MY_LIBRARY = "@show my library"; private static final Map supportedCommands = new HashMap<>(); + private static final TestableDialogsRunner testableDialogsRunner = new TestableDialogsRunner(); // for tests static { // special commands names in choose dialog @@ -89,6 +92,7 @@ public final class SystemUtil { supportedCommands.put(COMMAND_SHOW_OPPONENT_LIBRARY, "SHOW OPPONENT LIBRARY"); supportedCommands.put(COMMAND_SHOW_MY_HAND, "SHOW MY HAND"); supportedCommands.put(COMMAND_SHOW_MY_LIBRARY, "SHOW MY LIBRARY"); + supportedCommands.put(COMMAND_SHOW_TEST_DIALOGS, "SHOW TEST DIALOGS"); } private static final Pattern patternGroup = Pattern.compile("\\[(.+)\\]"); // [test new card] @@ -263,8 +267,13 @@ public final class SystemUtil { public static void executeCheatCommands(Game game, String commandsFilePath, Player feedbackPlayer) { // fake test ability for triggers and events - Ability fakeSourceAbilityTemplate = new SimpleStaticAbility(Zone.OUTSIDE, new InfoEffect("adding testing cards")); + Ability fakeSourceAbilityTemplate = new SimpleStaticAbility(Zone.OUTSIDE, new InfoEffect("fake ability")); fakeSourceAbilityTemplate.setControllerId(feedbackPlayer.getId()); + Card fakeSourceCard = feedbackPlayer.getLibrary().getFromTop(game); + if (fakeSourceCard != null) { + // set any existing card as source, so dialogs will show all GUI elements, including source and workable popup info + fakeSourceAbilityTemplate.setSourceId(fakeSourceCard.getId()); + } List errorsList = new ArrayList<>(); try { @@ -304,8 +313,9 @@ public final class SystemUtil { // add default commands initLines.add(0, String.format("[%s]", COMMAND_LANDS_ADD_TO_BATTLEFIELD)); initLines.add(1, String.format("[%s]", COMMAND_CARDS_ADD_TO_HAND)); - initLines.add(2, String.format("[%s]", COMMAND_UNDER_CONTROL_TAKE)); - initLines.add(3, String.format("[%s]", COMMAND_UNDER_CONTROL_GIVE)); + initLines.add(2, String.format("[%s]", COMMAND_SHOW_TEST_DIALOGS)); + initLines.add(3, String.format("[%s]", COMMAND_UNDER_CONTROL_TAKE)); + initLines.add(4, String.format("[%s]", COMMAND_UNDER_CONTROL_GIVE)); // collect all commands CommandGroup currentGroup = null; @@ -438,7 +448,7 @@ public final class SystemUtil { cardName = cardChoice.getChoice(); // amount - int cardAmount = feedbackPlayer.getAmount(1, 100, "How many [" + cardName + "] to add?", game); + int cardAmount = feedbackPlayer.getAmount(1, 100, "How many [" + cardName + "] to add?", null, game); if (cardAmount == 0) { break; } @@ -538,6 +548,11 @@ public final class SystemUtil { break; } + case COMMAND_SHOW_TEST_DIALOGS: { + testableDialogsRunner.selectAndShowTestableDialog(feedbackPlayer, fakeSourceAbilityTemplate.copy(), game, opponent); + break; + } + default: { String mes = String.format("Unknown system command: %s", runGroup.name); errorsList.add(mes); diff --git a/Mage.Common/src/main/java/mage/utils/testers/AnnounceXTestableDialog.java b/Mage.Common/src/main/java/mage/utils/testers/AnnounceXTestableDialog.java new file mode 100644 index 00000000000..bd346448472 --- /dev/null +++ b/Mage.Common/src/main/java/mage/utils/testers/AnnounceXTestableDialog.java @@ -0,0 +1,65 @@ +package mage.utils.testers; + +import mage.abilities.Ability; +import mage.game.Game; +import mage.players.Player; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Part of testable game dialogs + *

+ * Supported methods: + * - player.announceX() + * + * @author JayDi85 + */ +class AnnounceXTestableDialog extends BaseTestableDialog { + + boolean isYou; // who choose - you or opponent + boolean isMana; // reason - for mana payment or another value + int min; + int max; + + public AnnounceXTestableDialog(boolean isYou, boolean isMana, int min, int max) { + super(String.format("player.announceX(%s)", isYou ? "you" : "AI"), + String.format("%s from %d to %d", isMana ? "mana" : "cost", min, max), ""); + this.isYou = isYou; + this.isMana = isMana; + this.min = min; + this.max = max; + } + + @Override + public List showDialog(Player player, Ability source, Game game, Player opponent) { + Player choosingPlayer = this.isYou ? player : opponent; + String message = "message with html"; + int chooseRes; + chooseRes = choosingPlayer.announceX(this.min, this.max, message, game, source, this.isMana); + List result = new ArrayList<>(); + result.add(getGroup() + " - " + this.getName() + " selected " + chooseRes); + return result; + } + + static public void register(TestableDialogsRunner runner) { + List isYous = Arrays.asList(false, true); + List isManas = Arrays.asList(false, true); + for (boolean isYou : isYous) { + for (boolean isMana : isManas) { + runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 0)); + runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 1)); + runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 3)); + runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 50)); + runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 500)); + runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 1, 1)); + runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 1, 3)); + runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 1, 50)); + runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 3, 3)); + runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 3, 10)); + runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 10, 10)); + } + } + } +} diff --git a/Mage.Common/src/main/java/mage/utils/testers/BaseTestableDialog.java b/Mage.Common/src/main/java/mage/utils/testers/BaseTestableDialog.java new file mode 100644 index 00000000000..0ee78b67e51 --- /dev/null +++ b/Mage.Common/src/main/java/mage/utils/testers/BaseTestableDialog.java @@ -0,0 +1,74 @@ +package mage.utils.testers; + +import mage.constants.SubType; +import mage.filter.common.FilterCreaturePermanent; +import mage.game.Game; +import mage.players.Player; +import mage.target.Target; +import mage.target.common.TargetCreaturePermanent; +import mage.target.common.TargetPermanentOrPlayer; + +/** + * Part of testable game dialogs + * + * @author JayDi85 + */ +abstract class BaseTestableDialog implements TestableDialog { + + private final String group; + private final String name; + private final String description; + + public BaseTestableDialog(String group, String name, String description) { + this.group = group; + this.name = name; + this.description = description; + } + + @Override + final public String getGroup() { + return this.group; + } + + @Override + final public String getName() { + return this.name; + } + + @Override + final public String getDescription() { + return this.description; + } + + @Override + final public void showResult(Player player, Game game, String result) { + // show message with result + game.informPlayer(player, result); + // reset game and gui (in most use cases it must return to player's priority) + game.firePriorityEvent(player.getId()); + } + + static Target createAnyTarget(int min, int max) { + return createAnyTarget(min, max, false); + } + + private static Target createAnyTarget(int min, int max, boolean notTarget) { + return new TargetPermanentOrPlayer(min, max).withNotTarget(notTarget); + } + + static Target createCreatureTarget(int min, int max) { + return createCreatureTarget(min, max, false); + } + + private static Target createCreatureTarget(int min, int max, boolean notTarget) { + return new TargetCreaturePermanent(min, max).withNotTarget(notTarget); + } + + static Target createImpossibleTarget(int min, int max) { + return createImpossibleTarget(min, max, false); + } + + private static Target createImpossibleTarget(int min, int max, boolean notTarget) { + return new TargetCreaturePermanent(min, max, new FilterCreaturePermanent(SubType.TROOPER, "rare type"), notTarget); + } +} diff --git a/Mage.Common/src/main/java/mage/utils/testers/ChooseAmountTestableDialog.java b/Mage.Common/src/main/java/mage/utils/testers/ChooseAmountTestableDialog.java new file mode 100644 index 00000000000..b95937a88b9 --- /dev/null +++ b/Mage.Common/src/main/java/mage/utils/testers/ChooseAmountTestableDialog.java @@ -0,0 +1,111 @@ +package mage.utils.testers; + +import mage.abilities.Ability; +import mage.constants.Outcome; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetAmount; +import mage.target.Targets; +import mage.target.common.TargetAnyTargetAmount; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Part of testable game dialogs + *

+ * Supported methods: + * - player.chooseTarget(amount) + * + * @author JayDi85 + */ +class ChooseAmountTestableDialog extends BaseTestableDialog { + + boolean isYou; // who choose - you or opponent + int distributeAmount; + int targetsMin; + int targetsMax; + + public ChooseAmountTestableDialog(boolean isYou, String name, int distributeAmount, int targetsMin, int targetsMax) { + super(String.format("player.chooseTarget(%s, amount)", isYou ? "you" : "AI"), + name, + String.format("%d between %d-%d targets", distributeAmount, targetsMin, targetsMax)); + this.isYou = isYou; + this.distributeAmount = distributeAmount; + this.targetsMin = targetsMin; + this.targetsMax = targetsMax; + } + + @Override + public List showDialog(Player player, Ability source, Game game, Player opponent) { + TargetAmount choosingTarget = new TargetAnyTargetAmount(this.distributeAmount, this.targetsMin, this.targetsMax); + Player choosingPlayer = this.isYou ? player : opponent; + + // TODO: add "damage" word in ability text, so chooseTargetAmount an show diff dialog (due inner logic - distribute damage or 1/1) + boolean chooseRes = choosingPlayer.chooseTargetAmount(Outcome.Benefit, choosingTarget, source, game); + List result = new ArrayList<>(); + if (chooseRes) { + Targets.printDebugTargets(getGroup() + " - " + this.getName() + " - " + "TRUE", new Targets(choosingTarget), source, game, result); + } else { + Targets.printDebugTargets(getGroup() + " - " + this.getName() + " - " + "FALSE", new Targets(choosingTarget), source, game, result); + } + return result; + } + + static public void register(TestableDialogsRunner runner) { + // test game started with 2 players and 1 land on battlefield + // so it's better to use target limits like 0, 1, 3, 5, max + + List isYous = Arrays.asList(false, true); + + for (boolean isYou : isYous) { + // up to + runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 0, 0, 0)); + runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 0, 0, 1)); + runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 0, 0, 3)); + runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 0, 0, 5)); + // + runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to, invalid", 1, 0, 0)); + runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 1, 0, 1)); + runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 1, 0, 3)); + runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 1, 0, 5)); + // + runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to, invalid", 2, 0, 0)); + runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 2, 0, 1)); + runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 2, 0, 3)); + runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 2, 0, 5)); + // + runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to, invalid", 3, 0, 0)); + runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 3, 0, 1)); + runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 3, 0, 3)); + runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 3, 0, 5)); + // + runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to, invalid", 5, 0, 0)); + runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 5, 0, 1)); + runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 5, 0, 3)); + runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 5, 0, 5)); + + // need target + runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 0, 1, 1)); + runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 0, 1, 3)); + runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 0, 1, 5)); + // + runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 1, 1, 1)); + runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 1, 1, 3)); + runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 1, 1, 5)); + // + runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 2, 1, 1)); + runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 2, 1, 3)); + runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 2, 1, 5)); + // + runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 3, 1, 1)); + runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 3, 1, 3)); + runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 3, 1, 5)); + // + runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 5, 1, 1)); + runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 5, 1, 3)); + runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 5, 1, 5)); + } + } +} diff --git a/Mage.Common/src/main/java/mage/utils/testers/ChooseCardsTestableDialog.java b/Mage.Common/src/main/java/mage/utils/testers/ChooseCardsTestableDialog.java new file mode 100644 index 00000000000..8837d9b92ce --- /dev/null +++ b/Mage.Common/src/main/java/mage/utils/testers/ChooseCardsTestableDialog.java @@ -0,0 +1,111 @@ +package mage.utils.testers; + +import mage.abilities.Ability; +import mage.cards.Card; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.Targets; +import mage.target.common.TargetCardInHand; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Part of testable game dialogs + *

+ * Supported methods: + * - player.choose(cards) + * - player.chooseTarget(cards) + * + * @author JayDi85 + */ +class ChooseCardsTestableDialog extends BaseTestableDialog { + + TargetCard target; + boolean isTargetChoice; // how to choose - by xxx.choose or xxx.chooseTarget + boolean isYou; // who choose - you or opponent + + public ChooseCardsTestableDialog(boolean isTargetChoice, boolean notTarget, boolean isYou, String name, TargetCard target) { + super(String.format("%s(%s, %s, cards)", + isTargetChoice ? "player.chooseTarget" : "player.choose", + isYou ? "you" : "AI", + notTarget ? "not target" : "target"), name, target.toString()); + this.isTargetChoice = isTargetChoice; + this.target = target.withNotTarget(notTarget); + this.isYou = isYou; + } + + @Override + public List showDialog(Player player, Ability source, Game game, Player opponent) { + TargetCard choosingTarget = this.target.copy(); + Player choosingPlayer = this.isYou ? player : opponent; + + // make sure hand go first, so user can test diff type of targets + List all = new ArrayList<>(); + all.addAll(choosingPlayer.getHand().getCards(game)); + //all.addAll(choosingPlayer.getLibrary().getCards(game)); + Cards choosingCards = new CardsImpl(all.stream().limit(100).collect(Collectors.toList())); + + boolean chooseRes; + if (this.isTargetChoice) { + chooseRes = choosingPlayer.chooseTarget(Outcome.Benefit, choosingCards, choosingTarget, source, game); + } else { + chooseRes = choosingPlayer.choose(Outcome.Benefit, choosingCards, choosingTarget, source, game); + } + + List result = new ArrayList<>(); + if (chooseRes) { + Targets.printDebugTargets(getGroup() + " - " + this.getName() + " - " + "TRUE", new Targets(choosingTarget), source, game, result); + } else { + Targets.printDebugTargets(getGroup() + " - " + this.getName() + " - " + "FALSE", new Targets(choosingTarget), source, game, result); + } + return result; + } + + static public void register(TestableDialogsRunner runner) { + // test game started with 2 players and 7 cards in hand and 1 draw + // so it's better to use target limits like 0, 1, 3, 9, max + + FilterCard anyCard = StaticFilters.FILTER_CARD; + FilterCard impossibleCard = new FilterCard(); + impossibleCard.add(SubType.TROOPER.getPredicate()); + + List notTargets = Arrays.asList(false, true); + List isYous = Arrays.asList(false, true); + List isTargetChoices = Arrays.asList(false, true); + for (boolean notTarget : notTargets) { + for (boolean isYou : isYous) { + for (boolean isTargetChoice : isTargetChoices) { + runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "hand 0, X=0", new TargetCardInHand(0, 0, anyCard))); + runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "hand 1", new TargetCardInHand(1, anyCard))); + runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "hand 3", new TargetCardInHand(3, anyCard))); + runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "hand 9", new TargetCardInHand(9, anyCard))); + runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "hand 0-1", new TargetCardInHand(0, 1, anyCard))); + runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "hand 0-3", new TargetCardInHand(0, 3, anyCard))); + runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "hand 0-9", new TargetCardInHand(0, 9, anyCard))); + runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "hand any", new TargetCardInHand(0, Integer.MAX_VALUE, anyCard))); + runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "hand 1-3", new TargetCardInHand(1, 3, anyCard))); + runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "hand 2-3", new TargetCardInHand(2, 3, anyCard))); + runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "hand 2-9", new TargetCardInHand(2, 9, anyCard))); + runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "hand 8-9", new TargetCardInHand(8, 9, anyCard))); + // + runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "impossible 0, X=0", new TargetCardInHand(0, impossibleCard))); + runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "impossible 1", new TargetCardInHand(1, impossibleCard))); + runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "impossible 3", new TargetCardInHand(3, impossibleCard))); + runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "impossible 0-1", new TargetCardInHand(0, 1, impossibleCard))); + runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "impossible 0-3", new TargetCardInHand(0, 3, impossibleCard))); + runner.registerDialog(new ChooseCardsTestableDialog(isTargetChoice, notTarget, isYou, "impossible any", new TargetCardInHand(0, Integer.MAX_VALUE, impossibleCard))); + } + } + } + } +} diff --git a/Mage.Common/src/main/java/mage/utils/testers/ChooseChoiceTestableDialog.java b/Mage.Common/src/main/java/mage/utils/testers/ChooseChoiceTestableDialog.java new file mode 100644 index 00000000000..7e90c6cb124 --- /dev/null +++ b/Mage.Common/src/main/java/mage/utils/testers/ChooseChoiceTestableDialog.java @@ -0,0 +1,66 @@ +package mage.utils.testers; + +import mage.abilities.Ability; +import mage.choices.*; +import mage.constants.Outcome; +import mage.game.Game; +import mage.players.Player; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Part of testable game dialogs + *

+ * Supported methods: + * - player.choose(choice) + * + * @author JayDi85 + */ +class ChooseChoiceTestableDialog extends BaseTestableDialog { + + boolean isYou; // who choose - you or opponent + Choice choice; + + public ChooseChoiceTestableDialog(boolean isYou, String name, Choice choice) { + super(String.format("player.choose(%s, choice)", isYou ? "you" : "AI"), name, choice.getClass().getSimpleName()); + this.isYou = isYou; + this.choice = choice; + } + + @Override + public List showDialog(Player player, Ability source, Game game, Player opponent) { + Player choosingPlayer = this.isYou ? player : opponent; + Choice dialog = this.choice.copy(); + boolean chooseRes = choosingPlayer.choose(Outcome.Benefit, dialog, game); + + List result = new ArrayList<>(); + result.add(getGroup() + " - " + this.getName() + " - " + (chooseRes ? "TRUE" : "FALSE")); + result.add(""); + if (dialog.isKeyChoice()) { + String key = dialog.getChoiceKey(); + result.add(String.format("* selected key: %s (%s)", key, dialog.getKeyChoices().getOrDefault(key, null))); + } else { + result.add(String.format("* selected value: %s", dialog.getChoice())); + } + + return result; + } + + static public void register(TestableDialogsRunner runner) { + // TODO: add require option + // TODO: add ChoiceImpl with diff popup hints + List isYous = Arrays.asList(false, true); + for (boolean isYou : isYous) { + runner.registerDialog(new ChooseChoiceTestableDialog(isYou, "", new ChoiceBasicLandType())); + runner.registerDialog(new ChooseChoiceTestableDialog(isYou, "", new ChoiceCardType())); + runner.registerDialog(new ChooseChoiceTestableDialog(isYou, "", new ChoiceColor())); + runner.registerDialog(new ChooseChoiceTestableDialog(isYou, "", new ChoiceColorOrArtifact())); + runner.registerDialog(new ChooseChoiceTestableDialog(isYou, "", new ChoiceCreatureType(null, null))); // TODO: must be dynamic to pass game/source + runner.registerDialog(new ChooseChoiceTestableDialog(isYou, "", new ChoiceLandType())); + runner.registerDialog(new ChooseChoiceTestableDialog(isYou, "", new ChoiceLeftOrRight())); + runner.registerDialog(new ChooseChoiceTestableDialog(isYou, "", new ChoicePlaneswalkerType())); // TODO: must be dynamic to pass game/source + } + } +} diff --git a/Mage.Common/src/main/java/mage/utils/testers/ChoosePileTestableDialog.java b/Mage.Common/src/main/java/mage/utils/testers/ChoosePileTestableDialog.java new file mode 100644 index 00000000000..68bc9bbb265 --- /dev/null +++ b/Mage.Common/src/main/java/mage/utils/testers/ChoosePileTestableDialog.java @@ -0,0 +1,69 @@ +package mage.utils.testers; + +import mage.abilities.Ability; +import mage.cards.Card; +import mage.constants.Outcome; +import mage.game.Game; +import mage.players.Player; +import mage.util.CardUtil; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Part of testable game dialogs + *

+ * Supported methods: + * - player.choosePile() + * + * @author JayDi85 + */ +class ChoosePileTestableDialog extends BaseTestableDialog { + + boolean isYou; // who choose - you or opponent + int pileSize1; + int pileSize2; + + public ChoosePileTestableDialog(boolean isYou, int pileSize1, int pileSize2) { + super(String.format("player.choosePile(%s)", isYou ? "you" : "AI"), "pile sizes: " + pileSize1 + " and " + pileSize2, ""); + this.isYou = isYou; + this.pileSize1 = pileSize1; + this.pileSize2 = pileSize2; + } + + @Override + public List showDialog(Player player, Ability source, Game game, Player opponent) { + // TODO: it's ok to show broken title - must add html support in windows's title someday + String mainMessage = "main message with html" + CardUtil.getSourceLogName(game, source); + + // random piles (make sure it contain good amount of cards) + List all = new ArrayList<>(game.getCards()); + Collections.shuffle(all); + List pile1 = all.stream().limit(this.pileSize1).collect(Collectors.toList()); + Collections.shuffle(all); + List pile2 = all.stream().limit(this.pileSize2).collect(Collectors.toList()); + + Player choosingPlayer = this.isYou ? player : opponent; + boolean chooseRes = choosingPlayer.choosePile(Outcome.Benefit, mainMessage, pile1, pile2, game); + List result = new ArrayList<>(); + result.add(getGroup() + " - " + this.getName() + " - " + (chooseRes ? "TRUE" : "FALSE")); + result.add(" * selected pile: " + (chooseRes ? "pile 1" : "pile 2")); + return result; + } + + static public void register(TestableDialogsRunner runner) { + List isYous = Arrays.asList(false, true); + for (boolean isYou : isYous) { + runner.registerDialog(new ChoosePileTestableDialog(isYou, 3, 5)); + runner.registerDialog(new ChoosePileTestableDialog(isYou, 10, 10)); + runner.registerDialog(new ChoosePileTestableDialog(isYou, 30, 30)); + runner.registerDialog(new ChoosePileTestableDialog(isYou, 90, 90)); + runner.registerDialog(new ChoosePileTestableDialog(isYou, 0, 10)); + runner.registerDialog(new ChoosePileTestableDialog(isYou, 10, 0)); + runner.registerDialog(new ChoosePileTestableDialog(isYou, 0, 0)); + } + } +} diff --git a/Mage.Common/src/main/java/mage/utils/testers/ChooseTargetTestableDialog.java b/Mage.Common/src/main/java/mage/utils/testers/ChooseTargetTestableDialog.java new file mode 100644 index 00000000000..65284618774 --- /dev/null +++ b/Mage.Common/src/main/java/mage/utils/testers/ChooseTargetTestableDialog.java @@ -0,0 +1,123 @@ +package mage.utils.testers; + +import mage.abilities.Ability; +import mage.constants.Outcome; +import mage.game.Game; +import mage.players.Player; +import mage.target.Target; +import mage.target.Targets; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Part of testable game dialogs + *

+ * Supported methods: + * - target.choose() + * - target.chooseTarget() + * - player.choose(target) + * - player.chooseTarget(target) + * + * @author JayDi85 + */ +class ChooseTargetTestableDialog extends BaseTestableDialog { + + Target target; + boolean isPlayerChoice; // how to choose - by player.choose or by target.choose + boolean isTargetChoice; // how to choose - by xxx.choose or xxx.chooseTarget + boolean isYou; // who choose - you or opponent + + public ChooseTargetTestableDialog(boolean isPlayerChoice, boolean isTargetChoice, boolean notTarget, boolean isYou, String name, Target target) { + super(String.format("%s%s(%s, %s)", + isPlayerChoice ? "player.choose" : "target.choose", + isTargetChoice ? "Target" : "", // chooseTarget or choose + isYou ? "you" : "AI", + notTarget ? "not target" : "target"), name, target.toString()); + this.isPlayerChoice = isPlayerChoice; + this.isTargetChoice = isTargetChoice; + this.target = target.withNotTarget(notTarget); + this.isYou = isYou; + } + + @Override + public List showDialog(Player player, Ability source, Game game, Player opponent) { + Target choosingTarget = this.target.copy(); + Player choosingPlayer = this.isYou ? player : opponent; + + boolean chooseRes; + if (this.isPlayerChoice) { + // player.chooseXXX + if (this.isTargetChoice) { + chooseRes = choosingPlayer.chooseTarget(Outcome.Benefit, choosingTarget, source, game); + } else { + chooseRes = choosingPlayer.choose(Outcome.Benefit, choosingTarget, source, game); + } + } else { + // target.chooseXXX + if (this.isTargetChoice) { + chooseRes = choosingTarget.chooseTarget(Outcome.Benefit, choosingPlayer.getId(), source, game); + } else { + chooseRes = choosingTarget.choose(Outcome.Benefit, choosingPlayer.getId(), source, game); + } + } + + List result = new ArrayList<>(); + if (chooseRes) { + Targets.printDebugTargets(getGroup() + " - " + this.getName() + " - " + "TRUE", new Targets(choosingTarget), source, game, result); + } else { + Targets.printDebugTargets(getGroup() + " - " + this.getName() + " - " + "FALSE", new Targets(choosingTarget), source, game, result); + } + return result; + } + + static public void register(TestableDialogsRunner runner) { + // test game started with 2 players and 1 land on battlefield + // so it's better to use target limits like 0, 1, 3, 5, max + + List notTargets = Arrays.asList(false, true); + List isYous = Arrays.asList(false, true); + List isPlayerChoices = Arrays.asList(false, true); + List isTargetChoices = Arrays.asList(false, true); + for (boolean notTarget : notTargets) { + for (boolean isYou : isYous) { + for (boolean isTargetChoice : isTargetChoices) { + for (boolean isPlayerChoice : isPlayerChoices) { + runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 0 e.g. X=0", createAnyTarget(0, 0))); // simulate X=0 + runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 1", createAnyTarget(1, 1))); + runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 3", createAnyTarget(3, 3))); + runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 5", createAnyTarget(5, 5))); + runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any max", createAnyTarget(0, Integer.MAX_VALUE))); + runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 0-1", createAnyTarget(0, 1))); + runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 0-3", createAnyTarget(0, 3))); + runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 0-5", createAnyTarget(0, 5))); + runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 1-3", createAnyTarget(1, 3))); + runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 2-3", createAnyTarget(2, 3))); + runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 1-5", createAnyTarget(1, 5))); + runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 2-5", createAnyTarget(2, 5))); + runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 3-5", createAnyTarget(3, 5))); + runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 4-5", createAnyTarget(4, 5))); // impossible on 3 targets + // + runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 0, e.g. X=0", createImpossibleTarget(0, 0))); + runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 1", createImpossibleTarget(1, 1))); + runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 3", createImpossibleTarget(3, 3))); + runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 0-1", createImpossibleTarget(0, 1))); + runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 0-3", createImpossibleTarget(0, 3))); + runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 1-3", createImpossibleTarget(1, 3))); + runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 2-3", createImpossibleTarget(2, 3))); + runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible max", createImpossibleTarget(0, Integer.MAX_VALUE))); + // + /* + runner.registerDialog(new PlayerChooseTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "creatures 0, e.g. X=0", createCreatureTarget(0, 0))); // simulate X=0 + runner.registerDialog(new PlayerChooseTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "creatures 1", createCreatureTarget(1, 1))); + runner.registerDialog(new PlayerChooseTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "creatures 3", createCreatureTarget(3, 3))); + runner.registerDialog(new PlayerChooseTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "creatures 5", createCreatureTarget(5, 5))); + runner.registerDialog(new PlayerChooseTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "creatures max", createCreatureTarget(0, Integer.MAX_VALUE))); + */ + } + } + } + } + } +} diff --git a/Mage.Common/src/main/java/mage/utils/testers/ChooseUseTestableDialog.java b/Mage.Common/src/main/java/mage/utils/testers/ChooseUseTestableDialog.java new file mode 100644 index 00000000000..3e8137af32b --- /dev/null +++ b/Mage.Common/src/main/java/mage/utils/testers/ChooseUseTestableDialog.java @@ -0,0 +1,77 @@ +package mage.utils.testers; + +import mage.abilities.Ability; +import mage.constants.Outcome; +import mage.game.Game; +import mage.players.Player; +import mage.util.CardUtil; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Part of testable game dialogs + *

+ * Supported methods: + * - player.chooseUse() + * + * @author JayDi85 + */ +class ChooseUseTestableDialog extends BaseTestableDialog { + + boolean isYou; // who choose - you or opponent + String trueText; + String falseText; + String messageMain; + String messageAdditional; + + public ChooseUseTestableDialog(boolean isYou, String name, String trueText, String falseText, String messageMain, String messageAdditional) { + super(String.format("player.chooseUse(%s)", isYou ? "you" : "AI"), name + buildName(trueText, falseText, messageMain, messageAdditional), ""); + this.isYou = isYou; + this.trueText = trueText; + this.falseText = falseText; + this.messageMain = messageMain; + this.messageAdditional = messageAdditional; + } + + private static String buildName(String trueText, String falseText, String messageMain, String messageAdditional) { + String buttonsInfo = (trueText == null ? "default" : "custom") + "/" + (falseText == null ? "default" : "custom"); + String messagesInfo = (messageMain == null ? "-" : "main") + "/" + (messageAdditional == null ? "-" : "additional"); + return String.format("buttons: %s, messages: %s", buttonsInfo, messagesInfo); + } + + @Override + public List showDialog(Player player, Ability source, Game game, Player opponent) { + Player choosingPlayer = this.isYou ? player : opponent; + boolean chooseRes = choosingPlayer.chooseUse( + Outcome.Benefit, + messageMain, + messageAdditional == null ? null : messageAdditional + CardUtil.getSourceLogName(game, source), + trueText, + falseText, + source, + game + ); + List result = new ArrayList<>(); + result.add(chooseRes ? "TRUE" : "FALSE"); + return result; + } + + static public void register(TestableDialogsRunner runner) { + List isYous = Arrays.asList(false, true); + String trueButton = "true button"; + String falseButton = "false button"; + String mainMessage = "main message with html"; + String additionalMessage = "additional main message with html"; + for (boolean isYou : isYous) { + runner.registerDialog(new ChooseUseTestableDialog(isYou, "", null, null, mainMessage, additionalMessage)); + runner.registerDialog(new ChooseUseTestableDialog(isYou, "", trueButton, falseButton, mainMessage, additionalMessage)); + runner.registerDialog(new ChooseUseTestableDialog(isYou, "", null, falseButton, mainMessage, additionalMessage)); + runner.registerDialog(new ChooseUseTestableDialog(isYou, "", trueButton, null, mainMessage, additionalMessage)); + runner.registerDialog(new ChooseUseTestableDialog(isYou, "error ", trueButton, falseButton, null, additionalMessage)); + runner.registerDialog(new ChooseUseTestableDialog(isYou, "", trueButton, falseButton, mainMessage, null)); + runner.registerDialog(new ChooseUseTestableDialog(isYou, "error ", trueButton, falseButton, null, null)); + } + } +} diff --git a/Mage.Common/src/main/java/mage/utils/testers/GetAmountTestableDialog.java b/Mage.Common/src/main/java/mage/utils/testers/GetAmountTestableDialog.java new file mode 100644 index 00000000000..22a4659dd78 --- /dev/null +++ b/Mage.Common/src/main/java/mage/utils/testers/GetAmountTestableDialog.java @@ -0,0 +1,60 @@ +package mage.utils.testers; + +import mage.abilities.Ability; +import mage.game.Game; +import mage.players.Player; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Part of testable game dialogs + *

+ * Supported methods: + * - player.getAmount() + * + * @author JayDi85 + */ +class GetAmountTestableDialog extends BaseTestableDialog { + + boolean isYou; // who choose - you or opponent + int min; + int max; + + public GetAmountTestableDialog(boolean isYou, int min, int max) { + super(String.format("player.getAmount(%s)", isYou ? "you" : "AI"), + String.format("from %d to %d", min, max), ""); + this.isYou = isYou; + this.min = min; + this.max = max; + } + + @Override + public List showDialog(Player player, Ability source, Game game, Player opponent) { + Player choosingPlayer = this.isYou ? player : opponent; + String message = "message with html"; + int chooseRes; + chooseRes = choosingPlayer.getAmount(this.min, this.max, message, source, game); + List result = new ArrayList<>(); + result.add(getGroup() + " - " + this.getName() + " selected " + chooseRes); + return result; + } + + static public void register(TestableDialogsRunner runner) { + List isYous = Arrays.asList(false, true); + for (boolean isYou : isYous) { + runner.registerDialog(new GetAmountTestableDialog(isYou, 0, 0)); + runner.registerDialog(new GetAmountTestableDialog(isYou, 0, 1)); + runner.registerDialog(new GetAmountTestableDialog(isYou, 0, 3)); + runner.registerDialog(new GetAmountTestableDialog(isYou, 0, 50)); + runner.registerDialog(new GetAmountTestableDialog(isYou, 0, 500)); + runner.registerDialog(new GetAmountTestableDialog(isYou, 1, 1)); + runner.registerDialog(new GetAmountTestableDialog(isYou, 1, 3)); + runner.registerDialog(new GetAmountTestableDialog(isYou, 1, 50)); + runner.registerDialog(new GetAmountTestableDialog(isYou, 3, 3)); + runner.registerDialog(new GetAmountTestableDialog(isYou, 3, 10)); + runner.registerDialog(new GetAmountTestableDialog(isYou, 10, 10)); + } + } +} diff --git a/Mage.Common/src/main/java/mage/utils/testers/TestableDialog.java b/Mage.Common/src/main/java/mage/utils/testers/TestableDialog.java new file mode 100644 index 00000000000..1479ee9f306 --- /dev/null +++ b/Mage.Common/src/main/java/mage/utils/testers/TestableDialog.java @@ -0,0 +1,31 @@ +package mage.utils.testers; + +import mage.abilities.Ability; +import mage.game.Game; +import mage.players.Player; + +import java.util.List; + +/** + * Part of testable game dialogs + *

+ * How to use: + * - extends BaseTestableDialog + * - implement showDialog + * - create register with all possible sample dialogs + * - call register in main runner's constructor + * + * @author JayDi85 + */ +interface TestableDialog { + + String getGroup(); + + String getName(); + + String getDescription(); + + List showDialog(Player player, Ability source, Game game, Player opponent); + + void showResult(Player player, Game game, String result); +} diff --git a/Mage.Common/src/main/java/mage/utils/testers/TestableDialogsRunner.java b/Mage.Common/src/main/java/mage/utils/testers/TestableDialogsRunner.java new file mode 100644 index 00000000000..6217877dbce --- /dev/null +++ b/Mage.Common/src/main/java/mage/utils/testers/TestableDialogsRunner.java @@ -0,0 +1,202 @@ +package mage.utils.testers; + +import mage.abilities.Ability; +import mage.choices.Choice; +import mage.choices.ChoiceHintType; +import mage.choices.ChoiceImpl; +import mage.constants.Outcome; +import mage.game.Game; +import mage.players.Player; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Part of testable game dialogs + *

+ * Helper class to create additional options in cheat menu - allow to test any game dialogs with any settings + * Allow to call game dialogs from you or from your opponent, e.g. from AI player + *

+ * All existing human's choose dialogs (search by waitForResponse): + *

+ * Support of single dialogs (can be called inside effects): + * [x] choose(target) + * [x] choose(cards) + * [x] choose(choice) + * [x] chooseTarget(target) + * [x] chooseTarget(cards) + * [x] chooseTargetAmount + * [x] chooseUse + * [x] choosePile + * [x] announceX + * [x] getAmount + * [ ] getMultiAmountWithIndividualConstraints // TODO: implement + *

+ * Support of priority dialogs (can be called by game engine, some can be implemented in theory): + * --- priority + * --- playManaHandling + * --- activateSpecialAction + * --- activateAbility + * --- chooseAbilityForCast + * --- chooseLandOrSpellAbility + * --- chooseMode + * --- chooseMulligan + * --- chooseReplacementEffect + * --- chooseTriggeredAbility + * --- selectAttackers + * --- selectBlockers + * --- selectCombatGroup (part of selectBlockers) + *

+ * Support of outdated dialogs (not used anymore) + * --- announceRepetitions (part of removed macro feature) + * + * @author JayDi85 + */ +public class TestableDialogsRunner { + + private final List dialogs = new ArrayList<>(); + + static final int LAST_SELECTED_GROUP_ID = 997; + static final int LAST_SELECTED_DIALOG_ID = 998; + + // for better UX - save last selected options, so can return to it later + // it's ok to have it global, cause test mode for local and single user environment + static String lastSelectedGroup = null; + static TestableDialog lastSelectedDialog = null; + + + public TestableDialogsRunner() { + ChooseTargetTestableDialog.register(this); + ChooseCardsTestableDialog.register(this); + ChooseUseTestableDialog.register(this); + ChooseChoiceTestableDialog.register(this); + ChoosePileTestableDialog.register(this); + ChooseAmountTestableDialog.register(this); + AnnounceXTestableDialog.register(this); + GetAmountTestableDialog.register(this); + } + + void registerDialog(TestableDialog dialog) { + this.dialogs.add(dialog); + } + + public void selectAndShowTestableDialog(Player player, Ability source, Game game, Player opponent) { + // select group or fast links + List groups = this.dialogs.stream() + .map(TestableDialog::getGroup) + .distinct() + .sorted() + .collect(Collectors.toList()); + Choice choice = prepareSelectGroupChoice(groups); + player.choose(Outcome.Benefit, choice, game); + String needGroup = null; + TestableDialog needDialog = null; + if (choice.getChoiceKey() != null) { + int needIndex = Integer.parseInt(choice.getChoiceKey()); + if (needIndex == LAST_SELECTED_GROUP_ID && lastSelectedGroup != null) { + // fast link to group + needGroup = lastSelectedGroup; + } else if (needIndex == LAST_SELECTED_DIALOG_ID && lastSelectedDialog != null) { + // fast link to dialog + needGroup = lastSelectedDialog.getGroup(); + needDialog = lastSelectedDialog; + } else if (needIndex < groups.size()) { + // group + needGroup = groups.get(needIndex); + } + } + if (needGroup == null) { + return; + } + + // select dialog + if (needDialog == null) { + choice = prepareSelectDialogChoice(needGroup); + player.choose(Outcome.Benefit, choice, game); + if (choice.getChoiceKey() != null) { + int needIndex = Integer.parseInt(choice.getChoiceKey()); + if (needIndex < this.dialogs.size()) { + needDialog = this.dialogs.get(needIndex); + } + } + } + if (needDialog == null) { + return; + } + + // all fine, can show it and finish + lastSelectedGroup = needGroup; + lastSelectedDialog = needDialog; + List resInfo = needDialog.showDialog(player, source, game, opponent); + needDialog.showResult(player, game, String.join("
", resInfo)); + } + + private Choice prepareSelectGroupChoice(List groups) { + // try to choose group or fast links + + Choice choice = new ChoiceImpl(false); + choice.setMessage("Choose dialogs group to run"); + + // main groups + int recNumber = 0; + for (int i = 0; i < groups.size(); i++) { + recNumber++; + String group = groups.get(i); + choice.withItem( + String.valueOf(i), + String.format("%02d. %s", recNumber, group), + recNumber, + ChoiceHintType.TEXT, + String.join("
", group) + ); + } + + // fast link to last group + String lastGroupInfo = String.format(" -> last group: %s", lastSelectedGroup == null ? "not used" : lastSelectedGroup); + choice.withItem( + String.valueOf(LAST_SELECTED_GROUP_ID), + lastGroupInfo, + -2, + ChoiceHintType.TEXT, + lastGroupInfo + ); + + // fast link to last dialog + String lastDialogName = (lastSelectedDialog == null ? "not used" : String.format("%s - %s", + lastSelectedDialog.getName(), lastSelectedDialog.getDescription())); + String lastDialogInfo = String.format(" -> last dialog: %s", lastDialogName); + choice.withItem( + String.valueOf(LAST_SELECTED_DIALOG_ID), + lastDialogInfo, + -1, + ChoiceHintType.TEXT, + lastDialogInfo + ); + + return choice; + } + + private Choice prepareSelectDialogChoice(String needGroup) { + Choice choice = new ChoiceImpl(false); + choice.setMessage("Choose game dialog to run from " + needGroup); + int recNumber = 0; + for (int i = 0; i < this.dialogs.size(); i++) { + TestableDialog dialog = this.dialogs.get(i); + if (!dialog.getGroup().equals(needGroup)) { + continue; + } + recNumber++; + String info = String.format("%s - %s - %s", dialog.getGroup(), dialog.getName(), dialog.getDescription()); + choice.withItem( + String.valueOf(i), + String.format("%02d. %s", recNumber, info), + recNumber, + ChoiceHintType.TEXT, + String.join("
", info) + ); + } + return choice; + } +} + diff --git a/Mage.Common/src/main/java/mage/view/CombatGroupView.java b/Mage.Common/src/main/java/mage/view/CombatGroupView.java index c757ad6ff0d..5dbfdca857c 100644 --- a/Mage.Common/src/main/java/mage/view/CombatGroupView.java +++ b/Mage.Common/src/main/java/mage/view/CombatGroupView.java @@ -19,6 +19,7 @@ public class CombatGroupView implements Serializable { private final CardsView attackers = new CardsView(); private final CardsView blockers = new CardsView(); + private final boolean isBlocked; private String defenderName = ""; private final UUID defenderId; @@ -46,6 +47,7 @@ public class CombatGroupView implements Serializable { blockers.put(id, new PermanentView(blocker, game.getCard(blocker.getId()), null, game)); } } + isBlocked = combatGroup.getBlocked(); } public String getDefenderName() { @@ -63,4 +65,8 @@ public class CombatGroupView implements Serializable { public UUID getDefenderId() { return defenderId; } + + public boolean isBlocked() { + return isBlocked; + } } diff --git a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java index 6a2a2e1cd35..14ba9d0ea2b 100644 --- a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java +++ b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java @@ -399,7 +399,7 @@ public class ComputerPlayer6 extends ComputerPlayer { if (effect != null && stackObject.getControllerId().equals(playerId)) { Target target = effect.getTarget(); - if (!target.doneChoosing(game)) { + if (!target.isChoiceCompleted(game)) { for (UUID targetId : target.possibleTargets(stackObject.getControllerId(), stackObject.getStackAbility(), game)) { Game sim = game.createSimulationForAI(); StackAbility newAbility = (StackAbility) stackObject.copy(); @@ -848,10 +848,10 @@ public class ComputerPlayer6 extends ComputerPlayer { if (targets.isEmpty()) { return super.chooseTarget(outcome, cards, target, source, game); } - if (!target.doneChoosing(game)) { + if (!target.isChoiceCompleted(game)) { for (UUID targetId : targets) { target.addTarget(targetId, source, game); - if (target.doneChoosing(game)) { + if (target.isChoiceCompleted(game)) { targets.clear(); return true; } @@ -866,10 +866,10 @@ public class ComputerPlayer6 extends ComputerPlayer { if (targets.isEmpty()) { return super.choose(outcome, cards, target, source, game); } - if (!target.doneChoosing(game)) { + if (!target.isChoiceCompleted(game)) { for (UUID targetId : targets) { target.add(targetId, game); - if (target.doneChoosing(game)) { + if (target.isChoiceCompleted(game)) { targets.clear(); return true; } diff --git a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayerControllableProxy.java b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayerControllableProxy.java index e03ed1d623f..57e1bc4205b 100644 --- a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayerControllableProxy.java +++ b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayerControllableProxy.java @@ -2,7 +2,6 @@ package mage.player.ai; import mage.MageObject; import mage.abilities.*; -import mage.abilities.costs.VariableCost; import mage.abilities.costs.mana.ManaCost; import mage.cards.Card; import mage.cards.Cards; @@ -250,20 +249,11 @@ public class ComputerPlayerControllableProxy extends ComputerPlayer7 { } @Override - public int announceXMana(int min, int max, String message, Game game, Ability ability) { + public int announceX(int min, int max, String message, Game game, Ability source, boolean isManaPay) { if (isUnderMe(game)) { - return super.announceXMana(min, max, message, game, ability); + return super.announceX(min, max, message, game, source, isManaPay); } else { - return getControllingPlayer(game).announceXMana(min, max, message, game, ability); - } - } - - @Override - public int announceXCost(int min, int max, String message, Game game, Ability ability, VariableCost variableCost) { - if (isUnderMe(game)) { - return super.announceXCost(min, max, message, game, ability, variableCost); - } else { - return getControllingPlayer(game).announceXCost(min, max, message, game, ability, variableCost); + return getControllingPlayer(game).announceX(min, max, message, game, source, isManaPay); } } @@ -286,11 +276,11 @@ public class ComputerPlayerControllableProxy extends ComputerPlayer7 { } @Override - public int getAmount(int min, int max, String message, Game game) { + public int getAmount(int min, int max, String message, Ability source, Game game) { if (isUnderMe(game)) { - return super.getAmount(min, max, message, game); + return super.getAmount(min, max, message, source, game); } else { - return getControllingPlayer(game).getAmount(min, max, message, game); + return getControllingPlayer(game).getAmount(min, max, message, source, game); } } diff --git a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/SimulatedPlayer2.java b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/SimulatedPlayer2.java index 65ff643a73d..4452e3e682e 100644 --- a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/SimulatedPlayer2.java +++ b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/SimulatedPlayer2.java @@ -160,7 +160,7 @@ public final class SimulatedPlayer2 extends ComputerPlayer { } newAbility.adjustTargets(game); // add the different possible target option for the specific X value - if (!newAbility.getTargets().getUnchosen(game).isEmpty()) { + if (newAbility.getTargets().getNextUnchosen(game) != null) { addTargetOptions(options, newAbility, targetNum, game); } } 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 517e175d149..ace8620e6b2 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 @@ -5,7 +5,6 @@ import mage.ConditionalMana; import mage.MageObject; import mage.Mana; import mage.abilities.*; -import mage.abilities.costs.VariableCost; import mage.abilities.costs.mana.*; import mage.abilities.effects.Effect; import mage.abilities.effects.common.DamageTargetEffect; @@ -72,7 +71,7 @@ public class ComputerPlayer extends PlayerImpl { protected int PASSIVITY_PENALTY = 5; // Penalty value for doing nothing if some actions are available // debug only: set TRUE to debug simulation's code/games (on false sim thread will be stopped after few secs by timeout) - protected boolean COMPUTER_DISABLE_TIMEOUT_IN_GAME_SIMULATIONS = false; // DebugUtil.AI_ENABLE_DEBUG_MODE; + protected boolean COMPUTER_DISABLE_TIMEOUT_IN_GAME_SIMULATIONS = true; // DebugUtil.AI_ENABLE_DEBUG_MODE; // AI agents uses game simulation thread for all calcs and it's high CPU consumption // More AI threads - more parallel AI games can be calculate @@ -84,8 +83,7 @@ public class ComputerPlayer extends PlayerImpl { // * use your's CPU cores for best performance // TODO: add server config to control max AI threads (with CPU cores by default) // TODO: rework AI implementation to use multiple sims calculation instead one by one - final static int COMPUTER_MAX_THREADS_FOR_SIMULATIONS = 5;//DebugUtil.AI_ENABLE_DEBUG_MODE ? 1 : 5; - + final static int COMPUTER_MAX_THREADS_FOR_SIMULATIONS = 1;//DebugUtil.AI_ENABLE_DEBUG_MODE ? 1 : 5; private final transient Map unplayable = new TreeMap<>(); @@ -146,10 +144,12 @@ public class ComputerPlayer extends PlayerImpl { @Override public boolean choose(Outcome outcome, Target target, Ability source, Game game, Map options) { - if (log.isDebugEnabled()) { log.debug("choose: " + outcome.toString() + ':' + target.toString()); } + + boolean isAddedSomething = false; // must return true on any changes in targets, so game can ask next choose dialog until finish + // controller hints: // - target.getTargetController(), this.getId() -- player that must makes choices (must be same with this.getId) // - target.getAbilityController(), abilityControllerId -- affected player/controller for all actions/filters @@ -164,7 +164,7 @@ public class ComputerPlayer extends PlayerImpl { boolean required = target.isRequired(sourceId, game); Set possibleTargets = target.possibleTargets(abilityControllerId, source, game); - if (possibleTargets.isEmpty() || target.getTargets().size() >= target.getNumberOfTargets()) { + if (possibleTargets.isEmpty() || target.getTargets().size() >= target.getMinNumberOfTargets()) { required = false; } @@ -179,8 +179,10 @@ public class ComputerPlayer extends PlayerImpl { // discard not playable first if (!unplayable.isEmpty()) { for (int i = unplayable.size() - 1; i >= 0; i--) { - if (target.canTarget(abilityControllerId, unplayable.values().toArray(new Card[0])[i].getId(), source, game)) { - target.add(unplayable.values().toArray(new Card[0])[i].getId(), game); + UUID targetId = unplayable.values().toArray(new Card[0])[i].getId(); + if (target.canTarget(abilityControllerId, targetId, source, game) && !target.contains(targetId)) { + target.add(targetId, game); + isAddedSomething = true; if (target.isChosen(game)) { return true; } @@ -189,15 +191,17 @@ public class ComputerPlayer extends PlayerImpl { } if (!hand.isEmpty()) { for (int i = 0; i < hand.size(); i++) { - if (target.canTarget(abilityControllerId, hand.toArray(new UUID[0])[i], source, game)) { - target.add(hand.toArray(new UUID[0])[i], game); + UUID targetId = hand.toArray(new UUID[0])[i]; + if (target.canTarget(abilityControllerId, targetId, source, game) && !target.contains(targetId)) { + target.add(targetId, game); + isAddedSomething = true; if (target.isChosen(game)) { return true; } } } } - return false; + return isAddedSomething; } if (target.getOriginalTarget() instanceof TargetControlledPermanent @@ -209,18 +213,18 @@ public class ComputerPlayer extends PlayerImpl { Collections.reverse(targets); } for (Permanent permanent : targets) { - if (origTarget.canTarget(abilityControllerId, permanent.getId(), source, game, false) && !target.getTargets().contains(permanent.getId())) { + if (origTarget.canTarget(abilityControllerId, permanent.getId(), source, game, false) && !target.contains(permanent.getId())) { target.add(permanent.getId(), game); + isAddedSomething = true; if (target.isChosen(game)) { return true; } } } - return false; + return isAddedSomething; } if (target.getOriginalTarget() instanceof TargetPermanent) { - FilterPermanent filter = null; if (target.getOriginalTarget().getFilter() instanceof FilterPermanent) { filter = (FilterPermanent) target.getOriginalTarget().getFilter(); @@ -249,24 +253,25 @@ public class ComputerPlayer extends PlayerImpl { } for (Permanent permanent : targets) { - if (target.canTarget(abilityControllerId, permanent.getId(), source, game) && !target.getTargets().contains(permanent.getId())) { - // stop to add targets if not needed and outcome is no advantage for AI player - if (target.getNumberOfTargets() == target.getTargets().size()) { + if (target.canTarget(abilityControllerId, permanent.getId(), source, game) && !target.contains(permanent.getId())) { + // AI workaround to stop adding more targets in "up to" on bad outcome for itself + if (target.isChosen(game) && target.getMinNumberOfTargets() == target.getTargets().size()) { if (outcome.isGood() && hasOpponent(permanent.getControllerId(), game)) { - return true; + return isAddedSomething; } if (!outcome.isGood() && !hasOpponent(permanent.getControllerId(), game)) { - return true; + return isAddedSomething; } } // add the target target.add(permanent.getId(), game); - if (target.doneChoosing(game)) { + isAddedSomething = true; + if (target.isChosen(game)) { return true; } } } - return target.isChosen(game); + return isAddedSomething; } if (target.getOriginalTarget() instanceof TargetCardInHand @@ -282,11 +287,19 @@ public class ComputerPlayer extends PlayerImpl { && !cards.isEmpty()) { Card card = selectCard(abilityControllerId, cards, outcome, target, game); if (card != null) { - target.add(card.getId(), game); cards.remove(card); // selectCard don't remove cards (only on second+ tries) + if (!target.contains(card.getId())) { + target.add(card.getId(), game); // TODO: why it add as much as possible instead go to isChosen check like above? + isAddedSomething = true; + if (target.isChosen(game)) { + //return true; // TODO: why it add as much as possible instead go to isChosen check like above? + } + } + } else { + break; } } - return target.isChosen(game); + return isAddedSomething; } if (target.getOriginalTarget() instanceof TargetAnyTarget) { @@ -298,25 +311,31 @@ public class ComputerPlayer extends PlayerImpl { targets = threats(randomOpponentId, source, ((FilterAnyTarget) origTarget.getFilter()).getPermanentFilter(), game, target.getTargets()); } for (Permanent permanent : targets) { - List alreadyTargetted = target.getTargets(); - if (target.canTarget(abilityControllerId, permanent.getId(), source, game)) { - if (alreadyTargetted != null && !alreadyTargetted.contains(permanent.getId())) { - target.add(permanent.getId(), game); + if (target.canTarget(abilityControllerId, permanent.getId(), source, game) && !target.contains(permanent.getId())) { + target.add(permanent.getId(), game); + isAddedSomething = true; + if (target.isChosen(game)) { return true; } } } if (outcome.isGood()) { - if (target.canTarget(abilityControllerId, getId(), source, game)) { + if (target.canTarget(abilityControllerId, getId(), source, game) && !target.contains(getId())) { target.add(getId(), game); + isAddedSomething = true; + if (target.isChosen(game)) { + return true; + } + } + } else if (target.canTarget(abilityControllerId, randomOpponentId, source, game) && !target.contains(randomOpponentId)) { + target.add(randomOpponentId, game); + isAddedSomething = true; + if (target.isChosen(game)) { return true; } - } else if (target.canTarget(abilityControllerId, randomOpponentId, source, game)) { - target.add(randomOpponentId, game); - return true; } if (!required) { - return false; + return isAddedSomething; } } @@ -331,33 +350,43 @@ public class ComputerPlayer extends PlayerImpl { targets = opponentTargets; } for (Permanent permanent : targets) { - List alreadyTargeted = target.getTargets(); - if (target.canTarget(abilityControllerId, permanent.getId(), source, game)) { - if (alreadyTargeted != null && !alreadyTargeted.contains(permanent.getId())) { - target.add(permanent.getId(), game); - return true; - } + if (target.canTarget(abilityControllerId, permanent.getId(), source, game) && !target.contains(permanent.getId())) { + isAddedSomething = true; + target.add(permanent.getId(), game); + return true; } } if (outcome.isGood()) { - if (target.canTarget(abilityControllerId, getId(), source, game)) { + if (target.canTarget(abilityControllerId, getId(), source, game) && !target.contains(getId())) { target.add(getId(), game); + isAddedSomething = true; + if (target.isChosen(game)) { + return true; + } + } + } else if (target.canTarget(abilityControllerId, randomOpponentId, source, game) && !target.contains(randomOpponentId)) { + target.add(randomOpponentId, game); + isAddedSomething = true; + if (target.isChosen(game)) { return true; } - } else if (target.canTarget(abilityControllerId, randomOpponentId, source, game)) { + } + if (!target.isRequired(sourceId, game) || target.getMinNumberOfTargets() == 0) { + return isAddedSomething; // TODO: need research why it here (between diff type of targets) + } + if (target.canTarget(abilityControllerId, randomOpponentId, source, game) && !target.contains(randomOpponentId)) { target.add(randomOpponentId, game); - return true; + isAddedSomething = true; + if (target.isChosen(game)) { + return true; + } } - if (!target.isRequired(sourceId, game) || target.getNumberOfTargets() == 0) { - return false; - } - if (target.canTarget(abilityControllerId, randomOpponentId, source, game)) { - target.add(randomOpponentId, game); - return true; - } - if (target.canTarget(abilityControllerId, getId(), source, game)) { + if (target.canTarget(abilityControllerId, getId(), source, game) && !target.contains(getId())) { target.add(getId(), game); - return true; + isAddedSomething = true; + if (target.isChosen(game)) { + return true; + } } if (outcome.isGood()) { // no other valid targets so use a permanent targets = opponentTargets; @@ -365,15 +394,15 @@ public class ComputerPlayer extends PlayerImpl { targets = ownedTargets; } for (Permanent permanent : targets) { - List alreadyTargeted = target.getTargets(); - if (target.canTarget(abilityControllerId, permanent.getId(), source, game)) { - if (alreadyTargeted != null && !alreadyTargeted.contains(permanent.getId())) { - target.add(permanent.getId(), game); + if (target.canTarget(abilityControllerId, permanent.getId(), source, game) && !target.contains(permanent.getId())) { + target.add(permanent.getId(), game); + isAddedSomething = true; + if (target.isChosen(game)) { return true; } } } - return false; + return isAddedSomething; } if (target.getOriginalTarget() instanceof TargetCardInASingleGraveyard) { @@ -392,14 +421,19 @@ public class ComputerPlayer extends PlayerImpl { && !cards.isEmpty()) { Card card = selectCard(abilityControllerId, cards, outcome, target, game); if (card != null) { - target.add(card.getId(), game); cards.remove(card); // selectCard don't remove cards (only on second+ tries) + if (!target.contains(card.getId())) { + target.add(card.getId(), game); + isAddedSomething = true; + if (target.isChosen(game)) { + //return true; // TODO: why it add as much as possible instead go to isChosen check like above? + } + } } else { break; } } - - return target.isChosen(game); + return isAddedSomething; } if (target.getOriginalTarget() instanceof TargetCardInGraveyard @@ -419,36 +453,40 @@ public class ComputerPlayer extends PlayerImpl { && !cards.isEmpty()) { Card card = selectCard(abilityControllerId, cards, outcome, target, game); if (card != null) { - target.add(card.getId(), game); cards.remove(card); // selectCard don't remove cards (only on second+ tries) + if (!target.contains(card.getId())) { + target.add(card.getId(), game); + isAddedSomething = true; + if (target.isChosen(game)) { + //return true; // TODO: why it add as much as possible instead go to isChosen check like above? + } + } } else { break; } } - return target.isChosen(game); + return isAddedSomething; } if (target.getOriginalTarget() instanceof TargetCardInYourGraveyard || target.getOriginalTarget() instanceof TargetCardInASingleGraveyard) { - List alreadyTargeted = target.getTargets(); TargetCard originalTarget = (TargetCard) target.getOriginalTarget(); List cards = new ArrayList<>(game.getPlayer(abilityControllerId).getGraveyard().getCards(originalTarget.getFilter(), game)); while (!cards.isEmpty()) { Card card = selectCard(abilityControllerId, cards, outcome, target, game); - if (card != null && alreadyTargeted != null && !alreadyTargeted.contains(card.getId())) { + if (card != null && !target.contains(card.getId())) { target.add(card.getId(), game); + isAddedSomething = true; if (target.isChosen(game)) { return true; } } } - return false; + return isAddedSomething; } if (target.getOriginalTarget() instanceof TargetCardInExile) { - List alreadyTargeted = target.getTargets(); - FilterCard filter = null; if (target.getOriginalTarget().getFilter() instanceof FilterCard) { filter = (FilterCard) target.getOriginalTarget().getFilter(); @@ -461,14 +499,15 @@ public class ComputerPlayer extends PlayerImpl { List cards = game.getExile().getCards(filter, game); while (!cards.isEmpty()) { Card card = selectCard(abilityControllerId, cards, outcome, target, game); - if (card != null && alreadyTargeted != null && !alreadyTargeted.contains(card.getId())) { + if (card != null && !target.contains(card.getId())) { target.add(card.getId(), game); + isAddedSomething = true; if (target.isChosen(game)) { return true; } } } - return false; + return isAddedSomething; } if (target.getOriginalTarget() instanceof TargetSource) { @@ -477,52 +516,69 @@ public class ComputerPlayer extends PlayerImpl { for (UUID targetId : targets) { MageObject targetObject = game.getObject(targetId); if (targetObject != null) { - List alreadyTargeted = target.getTargets(); if (target.canTarget(abilityControllerId, targetObject.getId(), source, game)) { - if (alreadyTargeted != null && !alreadyTargeted.contains(targetObject.getId())) { + if (!target.contains(targetObject.getId())) { target.add(targetObject.getId(), game); - return true; + isAddedSomething = true; + if (target.isChosen(game)) { + return true; + } } } } } if (!required) { - return false; + return isAddedSomething; } throw new IllegalStateException("TargetSource wasn't handled in computer's choose method: " + target.getClass().getCanonicalName()); } if (target.getOriginalTarget() instanceof TargetPermanentOrSuspendedCard) { - Cards cards = new CardsImpl(possibleTargets); - List possibleCards = new ArrayList<>(cards.getCards(game)); - while (!target.isChosen(game) && !possibleCards.isEmpty()) { - Card card = selectCard(abilityControllerId, possibleCards, outcome, target, game); + List cards = new ArrayList<>(new CardsImpl(possibleTargets).getCards(game)); + while (!cards.isEmpty()) { + Card card = selectCard(abilityControllerId, cards, outcome, target, game); if (card != null) { - target.add(card.getId(), game); - possibleCards.remove(card); // selectCard don't remove cards (only on second+ tries) + cards.remove(card); // selectCard don't remove cards (only on second+ tries) + if (!target.contains(card.getId())) { + target.add(card.getId(), game); + isAddedSomething = true; + if (target.isChosen(game)) { + //return true; // TODO: why it add as much as possible instead go to isChosen check like above? + } + } + } else { + break; } } - return target.isChosen(game); + return isAddedSomething; } if (target.getOriginalTarget() instanceof TargetCard && (target.getZone() == Zone.COMMAND)) { // Hellkite Courser - List cardsInCommandZone = new ArrayList<>(); + List cards = new ArrayList<>(); for (Player player : game.getPlayers().values()) { for (Card card : game.getCommanderCardsFromCommandZone(player, CommanderCardType.COMMANDER_OR_OATHBREAKER)) { if (target.canTarget(abilityControllerId, card.getId(), source, game)) { - cardsInCommandZone.add(card); + cards.add(card); } } } - while (!target.isChosen(game) && !cardsInCommandZone.isEmpty()) { - Card pick = selectCard(abilityControllerId, cardsInCommandZone, outcome, target, game); - if (pick != null) { - target.add(pick.getId(), game); - cardsInCommandZone.remove(pick); + while (!cards.isEmpty()) { + Card card = selectCard(abilityControllerId, cards, outcome, target, game); + if (card != null) { + cards.remove(card); + if (!target.contains(card.getId())) { + target.add(card.getId(), game); + isAddedSomething = true; + if (target.isChosen(game)) { + //return true; // TODO: why it add as much as possible instead go to isChosen check like above? + } + } + } else { + break; } } - return target.isChosen(game); + return isAddedSomething; } throw new IllegalStateException("Target wasn't handled in computer's choose method: " + target.getClass().getCanonicalName()); @@ -534,6 +590,8 @@ public class ComputerPlayer extends PlayerImpl { log.debug("chooseTarget: " + outcome.toString() + ':' + target.toString()); } + boolean isAddedSomething = false; // must return true on any changes in targets, so game can ask next choose dialog until finish + // target - real target, make all changes and add targets to it // target.getOriginalTarget() - copy spell effect replaces original target with TargetWithAdditionalFilter // use originalTarget to get filters and target class info @@ -548,7 +606,7 @@ public class ComputerPlayer extends PlayerImpl { boolean required = target.isRequired(sourceId, game); Set possibleTargets = target.possibleTargets(abilityControllerId, source, game); - if (possibleTargets.isEmpty() || target.getTargets().size() >= target.getNumberOfTargets()) { + if (possibleTargets.isEmpty() || target.getTargets().size() >= target.getMinNumberOfTargets()) { required = false; } @@ -564,28 +622,36 @@ public class ComputerPlayer extends PlayerImpl { // Angel of Serenity trigger if (target.getOriginalTarget() instanceof TargetCardInGraveyardBattlefieldOrStack) { - Cards cards = new CardsImpl(possibleTargets); - List possibleCards = new ArrayList<>(cards.getCards(game)); - for (Card card : possibleCards) { + List cards = new ArrayList<>(new CardsImpl(possibleTargets).getCards(game)); + isAddedSomething = false; + for (Card card : cards) { // check permanents first; they have more intrinsic worth if (card instanceof Permanent) { Permanent p = ((Permanent) card); if (outcome.isGood() && p.isControlledBy(abilityControllerId)) { - if (target.canTarget(abilityControllerId, p.getId(), source, game)) { + if (target.canTarget(abilityControllerId, p.getId(), source, game) && !target.contains(p.getId())) { if (target.getTargets().size() >= target.getMaxNumberOfTargets()) { break; } target.addTarget(p.getId(), source, game); + isAddedSomething = true; + if (target.isChoiceCompleted(game)) { + return true; + } } } if (!outcome.isGood() && !p.isControlledBy(abilityControllerId)) { - if (target.canTarget(abilityControllerId, p.getId(), source, game)) { + if (target.canTarget(abilityControllerId, p.getId(), source, game) && !target.contains(p.getId())) { if (target.getTargets().size() >= target.getMaxNumberOfTargets()) { break; } target.addTarget(p.getId(), source, game); + isAddedSomething = true; + if (target.isChoiceCompleted(game)) { + return true; + } } } } @@ -593,29 +659,38 @@ public class ComputerPlayer extends PlayerImpl { if (game.getState().getZone(card.getId()) == Zone.GRAVEYARD) { if (outcome.isGood() && card.isOwnedBy(abilityControllerId)) { - if (target.canTarget(abilityControllerId, card.getId(), source, game)) { + if (target.canTarget(abilityControllerId, card.getId(), source, game) && !target.contains(card.getId())) { if (target.getTargets().size() >= target.getMaxNumberOfTargets()) { break; } target.addTarget(card.getId(), source, game); + isAddedSomething = true; + if (target.isChoiceCompleted(game)) { + return true; + } } } if (!outcome.isGood() && !card.isOwnedBy(abilityControllerId)) { - if (target.canTarget(abilityControllerId, card.getId(), source, game)) { + if (target.canTarget(abilityControllerId, card.getId(), source, game) && !target.contains(card.getId())) { if (target.getTargets().size() >= target.getMaxNumberOfTargets()) { break; } target.addTarget(card.getId(), source, game); + isAddedSomething = true; + if (target.isChoiceCompleted(game)) { + return true; + } } } } } - return target.isChosen(game); + return isAddedSomething; } if (target.getOriginalTarget() instanceof TargetDiscard || target.getOriginalTarget() instanceof TargetCardInHand) { + isAddedSomething = false; if (outcome.isGood()) { // good Cards cards = new CardsImpl(possibleTargets); @@ -625,10 +700,11 @@ public class ComputerPlayer extends PlayerImpl { && target.getMaxNumberOfTargets() > target.getTargets().size()) { Card card = selectBestCardTarget(cardsInHand, Collections.emptyList(), target, source, game); if (card != null) { - if (target.canTarget(abilityControllerId, card.getId(), source, game)) { + if (target.canTarget(abilityControllerId, card.getId(), source, game) && !target.contains(card.getId())) { target.addTarget(card.getId(), source, game); + isAddedSomething = true; cardsInHand.remove(card); - if (target.isChosen(game)) { + if (target.isChoiceCompleted(game)) { return true; } } @@ -639,9 +715,11 @@ public class ComputerPlayer extends PlayerImpl { findPlayables(game); for (Card card : unplayable.values()) { if (possibleTargets.contains(card.getId()) - && target.canTarget(abilityControllerId, card.getId(), source, game)) { + && target.canTarget(abilityControllerId, card.getId(), source, game) + && !target.contains(card.getId())) { target.addTarget(card.getId(), source, game); - if (target.isChosen(game)) { + isAddedSomething = true; + if (target.isChoiceCompleted(game)) { return true; } } @@ -649,16 +727,18 @@ public class ComputerPlayer extends PlayerImpl { if (!hand.isEmpty()) { for (Card card : hand.getCards(game)) { if (possibleTargets.contains(card.getId()) - && target.canTarget(abilityControllerId, card.getId(), source, game)) { + && target.canTarget(abilityControllerId, card.getId(), source, game) + && !target.contains(card.getId())) { target.addTarget(card.getId(), source, game); - if (target.isChosen(game)) { + isAddedSomething = true; + if (target.isChoiceCompleted(game)) { return true; } } } } } - return false; + return isAddedSomething; } if (target.getOriginalTarget() instanceof TargetControlledPermanent @@ -669,15 +749,17 @@ public class ComputerPlayer extends PlayerImpl { if (!outcome.isGood()) { Collections.reverse(targets); } + isAddedSomething = false; for (Permanent permanent : targets) { - if (target.canTarget(abilityControllerId, permanent.getId(), source, game)) { + if (target.canTarget(abilityControllerId, permanent.getId(), source, game) && !target.contains(permanent.getId())) { target.addTarget(permanent.getId(), source, game); - if (target.getNumberOfTargets() <= target.getTargets().size() && (!outcome.isGood() || target.getMaxNumberOfTargets() <= target.getTargets().size())) { - return true; + isAddedSomething = true; + if (target.getMinNumberOfTargets() <= target.getTargets().size() && (!outcome.isGood() || target.getMaxNumberOfTargets() <= target.getTargets().size())) { + return true; // TODO: need research - is it good optimization for good/bad effects? } } } - return target.isChosen(game); + return isAddedSomething; } @@ -687,7 +769,6 @@ public class ComputerPlayer extends PlayerImpl { // A) Having it here makes this function ridiculously long // B) Each time a new target type is added, people must remember to add it here if (target.getOriginalTarget() instanceof TargetPermanent) { - FilterPermanent filter = null; if (target.getOriginalTarget().getFilter() instanceof FilterPermanent) { filter = (FilterPermanent) target.getOriginalTarget().getFilter(); @@ -701,25 +782,30 @@ public class ComputerPlayer extends PlayerImpl { game, target, goodList, badList, allList); // use good list all the time and add maximum targets + isAddedSomething = false; for (Permanent permanent : goodList) { - if (target.canTarget(abilityControllerId, permanent.getId(), source, game)) { + if (target.canTarget(abilityControllerId, permanent.getId(), source, game) && !target.contains(permanent.getId())) { if (target.getTargets().size() >= target.getMaxNumberOfTargets()) { break; } target.addTarget(permanent.getId(), source, game); + isAddedSomething = true; } } // use bad list only on required target and add minimum targets if (required) { for (Permanent permanent : badList) { - if (target.getTargets().size() >= target.getMinNumberOfTargets()) { - break; + if (target.canTarget(abilityControllerId, permanent.getId(), source, game) && !target.contains(permanent.getId())) { + if (target.getTargets().size() >= target.getMinNumberOfTargets()) { + break; + } + target.addTarget(permanent.getId(), source, game); + isAddedSomething = true; } - target.addTarget(permanent.getId(), source, game); } } - return target.isChosen(game); + return isAddedSomething; } if (target.getOriginalTarget() instanceof TargetAnyTarget) { @@ -733,10 +819,10 @@ public class ComputerPlayer extends PlayerImpl { if (targets.isEmpty()) { if (outcome.isGood()) { - if (target.canTarget(abilityControllerId, getId(), source, game)) { + if (target.canTarget(abilityControllerId, getId(), source, game) && !target.contains(getId())) { return tryAddTarget(target, getId(), source, game); } - } else if (target.canTarget(abilityControllerId, randomOpponentId, source, game)) { + } else if (target.canTarget(abilityControllerId, randomOpponentId, source, game) && !target.contains(randomOpponentId)) { return tryAddTarget(target, randomOpponentId, source, game); } } @@ -746,7 +832,7 @@ public class ComputerPlayer extends PlayerImpl { } for (Permanent permanent : targets) { List alreadyTargeted = target.getTargets(); - if (target.canTarget(abilityControllerId, permanent.getId(), source, game)) { + if (target.canTarget(abilityControllerId, permanent.getId(), source, game) && !target.contains(permanent.getId())) { if (alreadyTargeted != null && !alreadyTargeted.contains(permanent.getId())) { tryAddTarget(target, permanent.getId(), source, game); } @@ -754,10 +840,10 @@ public class ComputerPlayer extends PlayerImpl { } if (outcome.isGood()) { - if (target.canTarget(abilityControllerId, getId(), source, game)) { + if (target.canTarget(abilityControllerId, getId(), source, game) && !target.contains(getId())) { return tryAddTarget(target, getId(), source, game); } - } else if (target.canTarget(abilityControllerId, randomOpponentId, source, game)) { + } else if (target.canTarget(abilityControllerId, randomOpponentId, source, game) && !target.contains(randomOpponentId)) { return tryAddTarget(target, randomOpponentId, source, game); } @@ -776,10 +862,10 @@ public class ComputerPlayer extends PlayerImpl { if (targets.isEmpty()) { if (outcome.isGood()) { - if (target.canTarget(abilityControllerId, getId(), source, game)) { + if (target.canTarget(abilityControllerId, getId(), source, game) && !target.contains(getId())) { return tryAddTarget(target, getId(), source, game); } - } else if (target.canTarget(abilityControllerId, randomOpponentId, source, game)) { + } else if (target.canTarget(abilityControllerId, randomOpponentId, source, game) && !target.contains(randomOpponentId)) { return tryAddTarget(target, randomOpponentId, source, game); } } @@ -789,7 +875,7 @@ public class ComputerPlayer extends PlayerImpl { } for (Permanent permanent : targets) { List alreadyTargeted = target.getTargets(); - if (target.canTarget(abilityControllerId, permanent.getId(), source, game)) { + if (target.canTarget(abilityControllerId, permanent.getId(), source, game) && !target.contains(permanent.getId())) { if (alreadyTargeted != null && !alreadyTargeted.contains(permanent.getId())) { return tryAddTarget(target, permanent.getId(), source, game); } @@ -815,10 +901,10 @@ public class ComputerPlayer extends PlayerImpl { // possible good/bad players if (targets.isEmpty()) { if (outcome.isGood()) { - if (target.canTarget(abilityControllerId, getId(), source, game)) { + if (target.canTarget(abilityControllerId, getId(), source, game) && !target.contains(getId())) { return tryAddTarget(target, getId(), source, game); } - } else if (target.canTarget(abilityControllerId, randomOpponentId, source, game)) { + } else if (target.canTarget(abilityControllerId, randomOpponentId, source, game) && !target.contains(randomOpponentId)) { return tryAddTarget(target, randomOpponentId, source, game); } } @@ -830,30 +916,27 @@ public class ComputerPlayer extends PlayerImpl { // try target permanent for (Permanent permanent : targets) { - List alreadyTargeted = target.getTargets(); - if (target.canTarget(abilityControllerId, permanent.getId(), source, game)) { - if (alreadyTargeted != null && !alreadyTargeted.contains(permanent.getId())) { - return tryAddTarget(target, permanent.getId(), source, game); - } + if (target.canTarget(abilityControllerId, permanent.getId(), source, game) && !target.contains(permanent.getId())) { + return tryAddTarget(target, permanent.getId(), source, game); } } // try target player as normal if (outcome.isGood()) { - if (target.canTarget(abilityControllerId, getId(), source, game)) { + if (target.canTarget(abilityControllerId, getId(), source, game) && !target.contains(getId())) { return tryAddTarget(target, getId(), source, game); } - } else if (target.canTarget(abilityControllerId, randomOpponentId, source, game)) { + } else if (target.canTarget(abilityControllerId, randomOpponentId, source, game) && !target.contains(randomOpponentId)) { return tryAddTarget(target, randomOpponentId, source, game); } // try target player as bad (bad on itself, good on opponent) for (UUID opponentId : game.getOpponents(getId(), true)) { - if (target.canTarget(abilityControllerId, opponentId, source, game)) { + if (target.canTarget(abilityControllerId, opponentId, source, game) && !target.contains(opponentId)) { return tryAddTarget(target, opponentId, source, game); } } - if (target.canTarget(abilityControllerId, getId(), source, game)) { + if (target.canTarget(abilityControllerId, getId(), source, game) && !target.contains(getId())) { return tryAddTarget(target, getId(), source, game); } @@ -866,7 +949,7 @@ public class ComputerPlayer extends PlayerImpl { cards.addAll(player.getGraveyard().getCards(game)); } Card card = selectCardTarget(abilityControllerId, cards, outcome, target, source, game); - if (card != null) { + if (card != null && !target.contains(card.getId())) { return tryAddTarget(target, card.getId(), source, game); } //if (!target.isRequired()) @@ -876,7 +959,7 @@ public class ComputerPlayer extends PlayerImpl { if (target.getOriginalTarget() instanceof TargetCardInLibrary) { List cards = new ArrayList<>(game.getPlayer(abilityControllerId).getLibrary().getCards(game)); Card card = selectCardTarget(abilityControllerId, cards, outcome, target, source, game); - if (card != null) { + if (card != null && !target.contains(card.getId())) { return tryAddTarget(target, card.getId(), source, game); } return false; @@ -884,14 +967,23 @@ public class ComputerPlayer extends PlayerImpl { if (target.getOriginalTarget() instanceof TargetCardInYourGraveyard) { List cards = new ArrayList<>(game.getPlayer(abilityControllerId).getGraveyard().getCards((FilterCard) target.getFilter(), game)); + isAddedSomething = false; while (!target.isChosen(game) && !cards.isEmpty()) { Card card = selectCardTarget(abilityControllerId, cards, outcome, target, source, game); if (card != null) { - target.addTarget(card.getId(), source, game); cards.remove(card); // selectCard don't remove cards (only on second+ tries) + if (!target.contains(card.getId())) { + target.addTarget(card.getId(), source, game); // TODO: why it add as much as possible instead go to isChosen check like above in choose? + isAddedSomething = true; + if (target.isChoiceCompleted(game)) { + return true; + } + } + } else { + break; } } - return target.isChosen(game); + return isAddedSomething; } if (target.getOriginalTarget() instanceof TargetSpell @@ -900,7 +992,8 @@ public class ComputerPlayer extends PlayerImpl { for (StackObject o : game.getStack()) { if (o instanceof Spell && !source.getId().equals(o.getStackAbility().getId()) - && target.canTarget(abilityControllerId, o.getStackAbility().getId(), source, game)) { + && target.canTarget(abilityControllerId, o.getStackAbility().getId(), source, game) + && !target.contains(o.getId())) { return tryAddTarget(target, o.getId(), source, game); } } @@ -924,17 +1017,17 @@ public class ComputerPlayer extends PlayerImpl { outcomeTargets = false; } for (Permanent permanent : targets) { - if (target.canTarget(abilityControllerId, permanent.getId(), source, game)) { + if (target.canTarget(abilityControllerId, permanent.getId(), source, game) && !target.contains(permanent.getId())) { target.addTarget(permanent.getId(), source, game); if (!outcomeTargets || target.getMaxNumberOfTargets() <= target.getTargets().size()) { - return true; + return true; // TODO: need logic research (e.g. select as much as possible on good outcome?) } } } if (!game.getStack().isEmpty()) { for (StackObject stackObject : game.getStack()) { if (stackObject instanceof Spell && source != null && !source.getId().equals(stackObject.getStackAbility().getId())) { - if (target.getFilter().match(stackObject, game)) { + if (target.getFilter().match(stackObject, game) && !target.contains(stackObject.getId())) { return tryAddTarget(target, stackObject.getId(), source, game); } } @@ -951,18 +1044,45 @@ public class ComputerPlayer extends PlayerImpl { cards.addAll(player.getGraveyard().getCards(game)); } } - Card card = selectCardTarget(abilityControllerId, cards, outcome, target, source, game); - if (card != null) { - return tryAddTarget(target, card.getId(), source, game); + isAddedSomething = false; + while (!cards.isEmpty()) { + Card card = selectCardTarget(abilityControllerId, cards, outcome, target, source, game); + if (card != null) { + cards.remove(card); // selectCard don't remove cards (only on second+ tries) + if (!target.contains(card.getId())) { + isAddedSomething = true; + target.addTarget(card.getId(), source, game); + if (target.isChoiceCompleted(game)) { + return true; + } + } + } else { + break; + } } //if (!target.isRequired()) - return false; + return isAddedSomething; } if (target.getOriginalTarget() instanceof TargetDefender) { - UUID randomDefender = RandomUtil.randomFromCollection(possibleTargets); - target.addTarget(randomDefender, source, game); - return target.isChosen(game); + List targets = new ArrayList<>(possibleTargets); + isAddedSomething = false; + while (!targets.isEmpty()) { + UUID randomDefender = RandomUtil.randomFromCollection(possibleTargets); + if (randomDefender != null) { + targets.remove(randomDefender); + if (!target.contains(randomDefender)) { + isAddedSomething = true; + target.addTarget(randomDefender, source, game); + if (target.isChoiceCompleted(game)) { + return true; + } + } + } else { + break; + } + } + return isAddedSomething; } if (target.getOriginalTarget() instanceof TargetCardInASingleGraveyard) { @@ -970,18 +1090,26 @@ public class ComputerPlayer extends PlayerImpl { for (Player player : game.getPlayers().values()) { cards.addAll(player.getGraveyard().getCards(game)); } - while (!target.isChosen(game) && !cards.isEmpty()) { + isAddedSomething = false; + while (!cards.isEmpty()) { Card card = selectCardTarget(abilityControllerId, cards, outcome, target, source, game); if (card != null) { - target.addTarget(card.getId(), source, game); cards.remove(card); // selectCard don't remove cards (only on second+ tries) + if (!target.contains(card.getId())) { + isAddedSomething = true; + target.addTarget(card.getId(), source, game); + if (target.isChoiceCompleted(game)) { + return true; + } + } + } else { + break; } } - return target.isChosen(game); + return isAddedSomething; } if (target.getOriginalTarget() instanceof TargetCardInExile) { - FilterCard filter = null; if (target.getOriginalTarget().getFilter() instanceof FilterCard) { filter = (FilterCard) target.getOriginalTarget().getFilter(); @@ -998,14 +1126,23 @@ public class ComputerPlayer extends PlayerImpl { cards.add(card); } } - while (!target.isChosen(game) && !cards.isEmpty()) { + isAddedSomething = false; + while (!cards.isEmpty()) { Card card = selectCardTarget(abilityControllerId, cards, outcome, target, source, game); if (card != null) { - target.addTarget(card.getId(), source, game); cards.remove(card); // selectCard don't remove cards (only on second+ tries) + if (!target.contains(card.getId())) { + isAddedSomething = true; + target.addTarget(card.getId(), source, game); + if (target.isChoiceCompleted(game)) { + return true; + } + } + } else { + break; } } - return target.isChosen(game); + return isAddedSomething; } if (target.getOriginalTarget() instanceof TargetActivatedAbility) { @@ -1016,22 +1153,43 @@ public class ComputerPlayer extends PlayerImpl { stackObjects.add(stackObject); } } - while (!target.isChosen(game) && !stackObjects.isEmpty()) { + while (!stackObjects.isEmpty()) { StackObject pick = stackObjects.get(0); if (pick != null) { - target.addTarget(pick.getId(), source, game); stackObjects.remove(0); + if (!target.contains(pick.getId())) { + isAddedSomething = true; + target.addTarget(pick.getId(), source, game); + if (target.isChoiceCompleted(game)) { + return true; + } + } + } else { + break; } } - return target.isChosen(game); + return isAddedSomething; } if (target.getOriginalTarget() instanceof TargetActivatedOrTriggeredAbility) { - Iterator iterator = target.possibleTargets(source.getControllerId(), source, game).iterator(); - while (!target.isChosen(game) && iterator.hasNext()) { - target.addTarget(iterator.next(), source, game); + List targets = new ArrayList<>(target.possibleTargets(source.getControllerId(), source, game)); + isAddedSomething = false; + while (!targets.isEmpty()) { + UUID id = targets.get(0); + if (id != null) { + targets.remove(0); + if (!target.contains(id)) { + isAddedSomething = true; + target.addTarget(id, source, game); + if (target.isChoiceCompleted(game)) { + return true; + } + } + } else { + break; + } } - return target.isChosen(game); + return isAddedSomething; } if (target.getOriginalTarget() instanceof TargetCardInGraveyardBattlefieldOrStack) { @@ -1040,23 +1198,42 @@ public class ComputerPlayer extends PlayerImpl { cards.addAll(player.getGraveyard().getCards(game)); cards.addAll(game.getBattlefield().getAllActivePermanents(new FilterPermanent(), player.getId(), game)); } - Card card = selectCardTarget(abilityControllerId, cards, outcome, target, source, game); - if (card != null) { - return tryAddTarget(target, card.getId(), source, game); + while (!cards.isEmpty()) { + Card card = selectCardTarget(abilityControllerId, cards, outcome, target, source, game); + if (card != null) { + cards.remove(card); // selectCard don't remove cards (only on second+ tries) + if (!target.contains(card.getId())) { + isAddedSomething = true; + target.addTarget(card.getId(), source, game); + if (target.isChoiceCompleted(game)) { + return true; + } + } + } else { + break; + } } } if (target.getOriginalTarget() instanceof TargetPermanentOrSuspendedCard) { - Cards cards = new CardsImpl(possibleTargets); - List possibleCards = new ArrayList<>(cards.getCards(game)); - while (!target.isChosen(game) && !possibleCards.isEmpty()) { - Card card = selectCardTarget(abilityControllerId, possibleCards, outcome, target, source, game); + List cards = new ArrayList<>(new CardsImpl(possibleTargets).getCards(game)); + isAddedSomething = false; + while (!cards.isEmpty()) { + Card card = selectCardTarget(abilityControllerId, cards, outcome, target, source, game); if (card != null) { - target.addTarget(card.getId(), source, game); - possibleCards.remove(card); // selectCard don't remove cards (only on second+ tries) + cards.remove(card); // selectCard don't remove cards (only on second+ tries) + if (!target.contains(card.getId())) { + isAddedSomething = true; + target.addTarget(card.getId(), source, game); + if (target.isChoiceCompleted(game)) { + return true; + } + } + } else { + break; } } - return target.isChosen(game); + return isAddedSomething; } throw new IllegalStateException("Target wasn't handled in computer's chooseTarget method: " + target.getClass().getCanonicalName()); @@ -1861,42 +2038,40 @@ public class ComputerPlayer extends PlayerImpl { } @Override - public int announceXMana(int min, int max, String message, Game game, Ability ability) { - // current logic - use max possible mana + public int announceX(int min, int max, String message, Game game, Ability source, boolean isManaPay) { + // fast calc on nothing to choose + if (min >= max) { + return min; + } // TODO: add good/bad effects support - // TODO: add simple game simulations like declare blocker? + // TODO: add simple game simulations like declare blocker (need to find only workable payment)? + // TODO: remove random logic or make it more stable (e.g. use same value in same game cycle) - int numAvailable = getAvailableManaProducers(game).size() - ability.getManaCosts().manaValue(); - if (numAvailable < 0) { - numAvailable = 0; + // protection from too big values + int realMin = min; + int realMax = max; + if (max == Integer.MAX_VALUE) { + realMax = Math.max(realMin, 10); // AI don't need huge values for X, cause can't use infinite combos + } + + int xValue; + if (isManaPay) { + // as X mana payment - due available mana + xValue = Math.max(0, getAvailableManaProducers(game).size() - source.getManaCostsToPay().getUnpaid().manaValue()); } else { - if (numAvailable < min) { - numAvailable = min; - } - if (numAvailable > max) { - numAvailable = max; - } + // as X actions + xValue = RandomUtil.nextInt(realMax + 1); } - return numAvailable; - } - @Override - public int announceXCost(int min, int max, String message, Game game, Ability ability, VariableCost variablCost) { - // current logic - use random non-zero value - - // TODO: add good/bad effects support - // TODO: remove random logic - - int value = RandomUtil.nextInt(CardUtil.overflowInc(max, 1)); - if (value < min) { - value = min; + if (xValue > realMax) { + xValue = realMax; } - if (value < max) { - // do not use zero values - value++; + if (xValue < realMin) { + xValue = realMin; } - return value; + + return xValue; } @Override @@ -2032,9 +2207,11 @@ public class ComputerPlayer extends PlayerImpl { @Override public boolean chooseTarget(Outcome outcome, Cards cards, TargetCard target, Ability source, Game game) { if (cards == null || cards.isEmpty()) { - return target.isRequired(source); + return false; } + boolean isAddedSomething = false; // must return true on any changes in targets, so game can ask next choose dialog until finish + // sometimes a target selection can be made from a player that does not control the ability UUID abilityControllerId = playerId; if (target.getTargetController() != null @@ -2044,20 +2221,28 @@ public class ComputerPlayer extends PlayerImpl { // we still use playerId when getting cards even if they don't control the search List cardChoices = new ArrayList<>(cards.getCards(target.getFilter(), playerId, source, game)); - while (!target.doneChoosing(game)) { + isAddedSomething = false; + while (!cardChoices.isEmpty()) { Card card = selectCardTarget(abilityControllerId, cardChoices, outcome, target, source, game); if (card != null) { - target.addTarget(card.getId(), source, game); - cardChoices.remove(card); + cardChoices.remove(card); // selectCard don't remove cards (only on second+ tries) + if (!target.contains(card.getId())) { + target.addTarget(card.getId(), source, game); + isAddedSomething = true; + if (target.isChoiceCompleted(game)) { + return true; + } + } } else { - // We don't have any valid target to choose so stop choosing - return target.getTargets().size() >= target.getNumberOfTargets(); + break; } - if (outcome == Outcome.Neutral && target.getTargets().size() > target.getNumberOfTargets() + (target.getMaxNumberOfTargets() - target.getNumberOfTargets()) / 2) { + + // try to fill as much as possible for good effect (see while end) or half for bad (see if) + if (target.isChosen(game) && !outcome.isGood() && target.getTargets().size() > target.getMinNumberOfTargets() + (target.getMaxNumberOfTargets() - target.getMinNumberOfTargets()) / 2) { return true; } } - return true; + return isAddedSomething; } @Override @@ -2075,19 +2260,20 @@ public class ComputerPlayer extends PlayerImpl { } List cardChoices = new ArrayList<>(cards.getCards(target.getFilter(), abilityControllerId, source, game)); - while (!target.doneChoosing(game)) { + do { Card card = selectCard(abilityControllerId, cardChoices, outcome, target, game); if (card != null) { target.add(card.getId(), game); cardChoices.remove(card); // selectCard don't remove cards (only on second+ tries) } else { // We don't have any valid target to choose so stop choosing - return target.getTargets().size() >= target.getNumberOfTargets(); + return target.isChosen(game); } - if (outcome == Outcome.Neutral && target.getTargets().size() > target.getNumberOfTargets() + (target.getMaxNumberOfTargets() - target.getNumberOfTargets()) / 2) { - return true; + // try to fill as much as possible for good effect (see while end) or half for bad (see if) + if (outcome == Outcome.Neutral && target.getTargets().size() > target.getMinNumberOfTargets() + (target.getMaxNumberOfTargets() - target.getMinNumberOfTargets()) / 2) { + return target.isChosen(game); } - } + } while (target.getTargets().size() < target.getMaxNumberOfTargets()); return true; } @@ -2182,9 +2368,15 @@ public class ComputerPlayer extends PlayerImpl { @Override // TODO: add AI support with outcome and replace random with min/max - public int getAmount(int min, int max, String message, Game game) { + public int getAmount(int min, int max, String message, Ability source, Game game) { log.debug("getAmount"); - if (min < max && min == 0) { + + // fast calc on nothing to choose + if (min >= max) { + return min; + } + + if (min == 0) { return RandomUtil.nextInt(CardUtil.overflowInc(max, 1)); } return min; @@ -2888,6 +3080,7 @@ public class ComputerPlayer extends PlayerImpl { return new ComputerPlayer(this); } + @Deprecated // TODO: replace by standard while cycle with cards.isempty and addTarget private boolean tryAddTarget(Target target, UUID id, Ability source, Game game) { // workaround to to check successfull targets add int before = target.getTargets().size(); @@ -2914,6 +3107,8 @@ public class ComputerPlayer extends PlayerImpl { /** * Sets a possible target player. Depends on bad/good outcome + *

+ * Return false on no more valid targets, e.g. can stop choose dialog * * @param targetingSource null on non-target choice like choose and source on targeting choice like chooseTarget */ @@ -2930,22 +3125,30 @@ public class ComputerPlayer extends PlayerImpl { if (target.getOriginalTarget() instanceof TargetOpponent) { if (targetingSource == null) { if (target.canTarget(randomOpponentId, game)) { - target.add(randomOpponentId, game); - return true; + if (!target.contains(randomOpponentId)) { + target.add(randomOpponentId, game); + return true; + } } } else if (target.canTarget(abilityControllerId, randomOpponentId, targetingSource, game)) { - target.addTarget(randomOpponentId, targetingSource, game); - return true; + if (!target.contains(randomOpponentId)) { + target.addTarget(randomOpponentId, targetingSource, game); + return true; + } } for (UUID possibleOpponentId : game.getOpponents(getId(), true)) { if (targetingSource == null) { if (target.canTarget(possibleOpponentId, game)) { - target.add(possibleOpponentId, game); - return true; + if (!target.contains(possibleOpponentId)) { + target.add(possibleOpponentId, game); + return true; + } } } else if (target.canTarget(abilityControllerId, possibleOpponentId, targetingSource, game)) { - target.addTarget(possibleOpponentId, targetingSource, game); - return true; + if (!target.contains(possibleOpponentId)) { + target.addTarget(possibleOpponentId, targetingSource, game); + return true; + } } } return false; @@ -2956,24 +3159,24 @@ public class ComputerPlayer extends PlayerImpl { if (affectedOutcome.isGood()) { if (targetingSource == null) { // good - if (target.canTarget(getId(), game)) { + if (target.canTarget(getId(), game) && !target.contains(getId())) { target.add(getId(), game); return true; } if (target.isRequired(sourceId, game)) { - if (target.canTarget(randomOpponentId, game)) { + if (target.canTarget(randomOpponentId, game) && !target.contains(randomOpponentId)) { target.add(randomOpponentId, game); return true; } } } else { // good - if (target.canTarget(abilityControllerId, getId(), targetingSource, game)) { + if (target.canTarget(abilityControllerId, getId(), targetingSource, game) && !target.contains(getId())) { target.addTarget(getId(), targetingSource, game); return true; } if (target.isRequired(sourceId, game)) { - if (target.canTarget(abilityControllerId, randomOpponentId, targetingSource, game)) { + if (target.canTarget(abilityControllerId, randomOpponentId, targetingSource, game) && !target.contains(randomOpponentId)) { target.addTarget(randomOpponentId, targetingSource, game); return true; } @@ -2981,24 +3184,24 @@ public class ComputerPlayer extends PlayerImpl { } } else if (targetingSource == null) { // bad - if (target.canTarget(randomOpponentId, game)) { + if (target.canTarget(randomOpponentId, game) && !target.contains(randomOpponentId)) { target.add(randomOpponentId, game); return true; } if (target.isRequired(sourceId, game)) { - if (target.canTarget(getId(), game)) { + if (target.canTarget(getId(), game) && !target.contains(getId())) { target.add(getId(), game); return true; } } } else { // bad - if (target.canTarget(abilityControllerId, randomOpponentId, targetingSource, game)) { + if (target.canTarget(abilityControllerId, randomOpponentId, targetingSource, game) && !target.contains(randomOpponentId)) { target.addTarget(randomOpponentId, targetingSource, game); return true; } if (required) { - if (target.canTarget(abilityControllerId, getId(), targetingSource, game)) { + if (target.canTarget(abilityControllerId, getId(), targetingSource, game) && !target.contains(getId())) { target.addTarget(getId(), targetingSource, game); return true; } diff --git a/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/SimulatedPlayerMCTS.java b/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/SimulatedPlayerMCTS.java index a7ac9164be1..944d9aa5135 100644 --- a/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/SimulatedPlayerMCTS.java +++ b/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/SimulatedPlayerMCTS.java @@ -375,11 +375,11 @@ public final class SimulatedPlayerMCTS extends MCTSPlayer { } @Override - public int getAmount(int min, int max, String message, Game game) { + public int getAmount(int min, int max, String message, Ability source, Game game) { if (this.isHuman()) { - return RandomUtil.nextInt(max - min) + min; + return RandomUtil.nextInt(max - min + 1) + min; } - return super.getAmount(min, max, message, game); + return super.getAmount(min, max, message, source, game); } } 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 34e9baec448..c0ab0e728db 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 @@ -41,7 +41,6 @@ import mage.players.net.UserData; import mage.target.Target; import mage.target.TargetAmount; import mage.target.TargetCard; -import mage.target.TargetPermanent; import mage.target.common.TargetAttackingCreature; import mage.target.common.TargetDefender; import mage.target.targetpointer.TargetPointer; @@ -148,7 +147,7 @@ public class HumanPlayer extends PlayerImpl { } /** - * Make fake player from any other + * Make fake player from any other */ public HumanPlayer(final PlayerImpl sourcePlayer, final PlayerResponse sourceResponse) { super(sourcePlayer); @@ -387,9 +386,10 @@ public class HumanPlayer extends PlayerImpl { @Override public boolean chooseMulligan(Game game) { if (!canCallFeedback(game)) { - return true; + return false; } + // TODO: rework to use existing chooseUse dialog, but do not remove chooseMulligan (AI must use special logic inside it) while (canRespond()) { int nextHandSize = game.mulliganDownTo(playerId); String cardsCountInfo = nextHandSize + (nextHandSize == 1 ? " card" : " cards"); @@ -427,7 +427,11 @@ public class HumanPlayer extends PlayerImpl { @Override public boolean chooseUse(Outcome outcome, String message, String secondMessage, String trueText, String falseText, Ability source, Game game) { if (!canCallFeedback(game)) { - return true; + return false; + } + + if (message == null) { + throw new IllegalArgumentException("Wrong code usage: main message is null"); } MessageToClient messageToClient = new MessageToClient(message, secondMessage); @@ -620,7 +624,7 @@ public class HumanPlayer extends PlayerImpl { @Override public boolean choose(Outcome outcome, Choice choice, Game game) { if (!canCallFeedback(game)) { - return true; + return false; } if (choice.isKeyChoice() && choice.getKeyChoices().isEmpty()) { @@ -683,7 +687,7 @@ public class HumanPlayer extends PlayerImpl { @Override public boolean choose(Outcome outcome, Target target, Ability source, Game game, Map options) { if (!canCallFeedback(game)) { - return true; + return false; } // choose one or multiple permanents @@ -696,98 +700,85 @@ public class HumanPlayer extends PlayerImpl { options = new HashMap<>(); } + // stop on completed, e.g. X=0 + if (target.isChoiceCompleted(abilityControllerId, source, game)) { + return false; + } + while (canRespond()) { - Set possibleTargetIds = target.possibleTargets(abilityControllerId, source, game); - if (possibleTargetIds == null || possibleTargetIds.isEmpty()) { - return target.getTargets().size() >= target.getNumberOfTargets(); - } boolean required = target.isRequired(source != null ? source.getSourceId() : null, game); - if (target.getTargets().size() >= target.getNumberOfTargets()) { + + // enable done button after min targets selected + if (target.getTargets().size() >= target.getMinNumberOfTargets()) { required = false; } - UUID responseId = target.tryToAutoChoose(abilityControllerId, source, game); + // stop on impossible selection + if (required && !target.canChoose(abilityControllerId, source, game)) { + break; + } - // responseId is null if a choice couldn't be automatically made - if (responseId == null) { - List chosenTargets = target.getTargets(); - options.put("chosenTargets", (Serializable) chosenTargets); + // stop on nothing to choose + Set possibleTargets = target.possibleTargets(abilityControllerId, source, game); + if (required && possibleTargets.isEmpty()) { + break; + } + + // MAKE A CHOICE + UUID autoChosenId = target.tryToAutoChoose(abilityControllerId, source, game); + if (autoChosenId != null && !target.contains(autoChosenId)) { + // auto-choose + target.add(autoChosenId, game); + // continue to next target (example: auto-choose must fill min/max = 2 from 2 possible cards) + } else { + // manual choose + options.put("chosenTargets", (Serializable) target.getTargets()); prepareForResponse(game); if (!isExecutingMacro()) { - game.fireSelectTargetEvent(getId(), new MessageToClient(target.getMessage(game), getRelatedObjectName(source, game)), possibleTargetIds, required, getOptions(target, options)); + game.fireSelectTargetEvent(getId(), new MessageToClient(target.getMessage(game), getRelatedObjectName(source, game)), possibleTargets, required, getOptions(target, options)); } waitForResponse(game); - responseId = getFixedResponseUUID(game); - } + UUID responseId = getFixedResponseUUID(game); - if (responseId != null) { - // selected some target + if (responseId != null) { + // selected something - // remove selected - if (target.getTargets().contains(responseId)) { - target.remove(responseId); - continue; - } + // remove selected + if (target.contains(responseId)) { + target.remove(responseId); + continue; + } - if (!possibleTargetIds.contains(responseId)) { - continue; - } - - if (target instanceof TargetPermanent) { - if (((TargetPermanent) target).canTarget(abilityControllerId, responseId, source, game, false)) { + if (possibleTargets.contains(responseId) && target.canTarget(getId(), responseId, source, game)) { target.add(responseId, game); - if (target.doneChoosing(game)) { - return true; + if (target.isChoiceCompleted(abilityControllerId, source, game)) { + break; } } } else { - MageObject object = game.getObject(source); - if (object instanceof Ability) { - if (target.canTarget(responseId, (Ability) object, game)) { - if (target.getTargets().contains(responseId)) { // if already included remove it with - target.remove(responseId); - } else { - target.addTarget(responseId, (Ability) object, game); - if (target.doneChoosing(game)) { - return true; - } - } - } - } else if (target.canTarget(responseId, game)) { - if (target.getTargets().contains(responseId)) { // if already included remove it with - target.remove(responseId); - } else { - target.addTarget(responseId, null, game); - if (target.doneChoosing(game)) { - return true; - } + // stop on done/cancel button press + if (target.isChosen(game)) { + break; + } else { + if (!required) { + // can stop at any moment + break; } } } - } else { - // send other command like cancel or done (??sends other commands like concede??) - - // auto-complete on all selected - if (target.getTargets().size() >= target.getNumberOfTargets()) { - return true; - } - - // cancel/done button - if (!required) { - return false; - } + // continue to next target } } - return false; + return target.isChosen(game) && target.getTargets().size() > 0; } @Override public boolean chooseTarget(Outcome outcome, Target target, Ability source, Game game) { if (!canCallFeedback(game)) { - return true; + return false; } // choose one or multiple targets @@ -799,64 +790,67 @@ public class HumanPlayer extends PlayerImpl { Map options = new HashMap<>(); while (canRespond()) { - Set possibleTargetIds = target.possibleTargets(abilityControllerId, source, game); + Set possibleTargets = target.possibleTargets(abilityControllerId, source, game); boolean required = target.isRequired(source != null ? source.getSourceId() : null, game); - if (possibleTargetIds.isEmpty() - || target.getTargets().size() >= target.getNumberOfTargets()) { + if (possibleTargets.isEmpty() + || target.getTargets().size() >= target.getMinNumberOfTargets()) { required = false; } + // auto-choose UUID responseId = target.tryToAutoChoose(abilityControllerId, source, game); - // responseId is null if a choice couldn't be automatically made + // manual choice if (responseId == null) { - - - List chosenTargets = target.getTargets(); - options.put("chosenTargets", (Serializable) chosenTargets); + options.put("chosenTargets", (Serializable) target.getTargets()); prepareForResponse(game); if (!isExecutingMacro()) { game.fireSelectTargetEvent(getId(), new MessageToClient(target.getMessage(game), getRelatedObjectName(source, game)), - possibleTargetIds, required, getOptions(target, options)); + possibleTargets, required, getOptions(target, options)); } waitForResponse(game); responseId = getFixedResponseUUID(game); } if (responseId != null) { - // remove selected - if (target.getTargets().contains(responseId)) { + // remove old target + if (target.contains(responseId)) { target.remove(responseId); continue; } - if (possibleTargetIds.contains(responseId)) { + // add new target + if (possibleTargets.contains(responseId)) { if (target.canTarget(abilityControllerId, responseId, source, game)) { target.addTarget(responseId, source, game); - if (target.doneChoosing(game)) { + if (target.isChoiceCompleted(abilityControllerId, source, game)) { return true; } } } } else { - if (target.getTargets().size() >= target.getNumberOfTargets()) { - return true; - } - if (!required) { + // done or cancel button pressed + if (target.isChosen(game)) { + // try to finish return false; + } else { + if (!required) { + // can stop at any moment + return false; + } } } } - return false; + return target.isChosen(game) && target.getTargets().size() > 0; } private Map getOptions(Target target, Map options) { if (options == null) { options = new HashMap<>(); } - if (target.getTargets().size() >= target.getNumberOfTargets() + if (target.getTargets().size() >= target.getMinNumberOfTargets() && !options.containsKey("UI.right.btn.text")) { options.put("UI.right.btn.text", "Done"); } @@ -867,10 +861,10 @@ public class HumanPlayer extends PlayerImpl { @Override public boolean choose(Outcome outcome, Cards cards, TargetCard target, Ability source, Game game) { if (!canCallFeedback(game)) { - return true; + return false; } - // choose one or multiple cards + // ignore bad state if (cards == null || cards.isEmpty()) { return false; } @@ -884,30 +878,37 @@ public class HumanPlayer extends PlayerImpl { } while (canRespond()) { - boolean required = target.isRequired(source != null ? source.getSourceId() : null, game); - int count = cards.count(target.getFilter(), abilityControllerId, source, game); - if (count == 0 - || target.getTargets().size() >= target.getNumberOfTargets()) { - required = false; - } - List chosenTargets = target.getTargets(); List possibleTargets = new ArrayList<>(); for (UUID cardId : cards) { if (target.canTarget(abilityControllerId, cardId, source, cards, game)) { possibleTargets.add(cardId); } } + + boolean required = target.isRequired(source != null ? source.getSourceId() : null, game); + int count = cards.count(target.getFilter(), abilityControllerId, source, game); + if (count == 0 + || target.getTargets().size() >= target.getMinNumberOfTargets()) { + required = false; + } + // if nothing to choose then show dialog (user must see non selectable items and click on any of them) + // TODO: need research - is it used? if (required && possibleTargets.isEmpty()) { required = false; } - UUID responseId = target.tryToAutoChoose(abilityControllerId, source, game, possibleTargets); - - if (responseId == null) { + // MAKE A CHOICE + UUID autoChosenId = target.tryToAutoChoose(abilityControllerId, source, game, possibleTargets); + if (autoChosenId != null && !target.contains(autoChosenId)) { + // auto-choose + target.add(autoChosenId, game); + // continue to next target (example: auto-choose must fill min/max = 2 from 2 possible cards) + } else { + // manual choose Map options = getOptions(target, null); - options.put("chosenTargets", (Serializable) chosenTargets); + options.put("chosenTargets", (Serializable) target.getTargets()); if (!possibleTargets.isEmpty()) { options.put("possibleTargets", (Serializable) possibleTargets); } @@ -918,27 +919,36 @@ public class HumanPlayer extends PlayerImpl { } waitForResponse(game); - responseId = getFixedResponseUUID(game); - } + UUID responseId = getFixedResponseUUID(game); - if (responseId != null) { - if (target.getTargets().contains(responseId)) { // if already included remove it with - target.remove(responseId); - } else { - if (target.canTarget(abilityControllerId, responseId, source, cards, game)) { + if (responseId != null) { + // selected something + + // remove selected + if (target.contains(responseId)) { + target.remove(responseId); + continue; + } + + if (possibleTargets.contains(responseId) && target.canTarget(getId(), responseId, source, cards, game)) { target.add(responseId, game); - if (target.doneChoosing(game)) { + if (target.isChoiceCompleted(abilityControllerId, source, game)) { return true; } } + } else { + // done or cancel button pressed + if (target.isChosen(game)) { + // try to finish + return false; + } else { + if (!required) { + // can stop at any moment + return false; + } + } } - } else { - if (target.getTargets().size() >= target.getNumberOfTargets()) { - return true; - } - if (!required) { - return false; - } + // continue to next target } } @@ -949,7 +959,7 @@ public class HumanPlayer extends PlayerImpl { @Override public boolean chooseTarget(Outcome outcome, Cards cards, TargetCard target, Ability source, Game game) { if (!canCallFeedback(game)) { - return true; + return false; } if (cards == null || cards.isEmpty()) { @@ -968,7 +978,7 @@ public class HumanPlayer extends PlayerImpl { boolean required = target.isRequiredExplicitlySet() ? target.isRequired() : target.isRequired(source); int count = cards.count(target.getFilter(), abilityControllerId, source, game); if (count == 0 - || target.getTargets().size() >= target.getNumberOfTargets()) { + || target.getTargets().size() >= target.getMinNumberOfTargets()) { required = false; } @@ -978,17 +988,16 @@ public class HumanPlayer extends PlayerImpl { possibleTargets.add(cardId); } } - // if nothing to choose then show dialog (user must see non selectable items and click on any of them) - if (required && possibleTargets.isEmpty()) { + // if nothing to choose then show dialog (user must see non-selectable items and click on any of them) + if (possibleTargets.isEmpty()) { required = false; } UUID responseId = target.tryToAutoChoose(abilityControllerId, source, game, possibleTargets); if (responseId == null) { - List chosenTargets = target.getTargets(); Map options = getOptions(target, null); - options.put("chosenTargets", (Serializable) chosenTargets); + options.put("chosenTargets", (Serializable) target.getTargets()); if (!possibleTargets.isEmpty()) { options.put("possibleTargets", (Serializable) possibleTargets); @@ -1004,16 +1013,16 @@ public class HumanPlayer extends PlayerImpl { } if (responseId != null) { - if (target.getTargets().contains(responseId)) { // if already included remove it + if (target.contains(responseId)) { // if already included remove it target.remove(responseId); } else if (target.canTarget(abilityControllerId, responseId, source, cards, game)) { target.addTarget(responseId, source, game); - if (target.doneChoosing(game)) { + if (target.isChoiceCompleted(abilityControllerId, source, game)) { return true; } } } else { - if (target.getTargets().size() >= target.getNumberOfTargets()) { + if (target.getTargets().size() >= target.getMinNumberOfTargets()) { return true; } if (!required) { @@ -1030,7 +1039,7 @@ public class HumanPlayer extends PlayerImpl { // choose amount // human can choose or un-choose MULTIPLE targets at once if (!canCallFeedback(game)) { - return true; + return false; } if (source == null) { @@ -1057,11 +1066,12 @@ public class HumanPlayer extends PlayerImpl { // 2. Distribute amount between selected targets // 1. Select targets + // TODO: rework to use existing chooseTarget instead custom select? while (canRespond()) { Set possibleTargetIds = target.possibleTargets(abilityControllerId, source, game); boolean required = target.isRequired(source.getSourceId(), game); if (possibleTargetIds.isEmpty() - || target.getSize() >= target.getNumberOfTargets()) { + || target.getSize() >= target.getMinNumberOfTargets()) { required = false; } @@ -1069,7 +1079,6 @@ public class HumanPlayer extends PlayerImpl { // responseId is null if a choice couldn't be automatically made if (responseId == null) { - List chosenTargets = target.getTargets(); List possibleTargets = new ArrayList<>(); for (UUID targetId : possibleTargetIds) { if (target.canTarget(abilityControllerId, targetId, source, game)) { @@ -1083,7 +1092,7 @@ public class HumanPlayer extends PlayerImpl { // selected Map options = getOptions(target, null); - options.put("chosenTargets", (Serializable) chosenTargets); + options.put("chosenTargets", (Serializable) target.getTargets()); if (!possibleTargets.isEmpty()) { options.put("possibleTargets", (Serializable) possibleTargets); } @@ -1185,17 +1194,8 @@ public class HumanPlayer extends PlayerImpl { // TODO: change pass and other states like passedUntilStackResolved for controlling player, not for "this" // TODO: check and change all "this" to controling player calls, many bugs with hand, mana, skips - https://github.com/magefree/mage/issues/2088 // TODO: use controlling player in all choose dialogs (and canRespond too, what's with take control of player AI?!) - UserData controllingUserData = this.userData; + UserData controllingUserData = this.getControllingPlayersUserData(game); if (canRespond()) { - if (!isGameUnderControl()) { - Player player = game.getPlayer(getTurnControlledBy()); - if (player instanceof HumanPlayer) { - controllingUserData = player.getUserData(); - } else { - // TODO: add computer opponent here?! - } - } - // TODO: check that all skips and stops used from real controlling player // like holdingPriority (is it a bug here?) if (getJustActivatedType() != null && !holdingPriority) { @@ -1489,7 +1489,7 @@ public class HumanPlayer extends PlayerImpl { @Override public TriggeredAbility chooseTriggeredAbility(java.util.List abilities, Game game) { - // choose triggered abilitity from list + // choose triggered ability from list if (!canCallFeedback(game)) { return abilities.isEmpty() ? null : abilities.get(0); } @@ -1620,7 +1620,7 @@ public class HumanPlayer extends PlayerImpl { protected boolean playManaHandling(Ability abilityToCast, ManaCost unpaid, String promptText, Game game) { // choose mana to pay (from permanents or from pool) if (!canCallFeedback(game)) { - return true; + return false; } // TODO: make canRespond cycle? @@ -1687,64 +1687,39 @@ public class HumanPlayer extends PlayerImpl { } /** - * Gets the amount of mana the player want to spent for a x spell - * - * @param min - * @param max - * @param message - * @param ability - * @param game - * @return + * Gets the amount of mana the player want to spend for an x spell */ @Override - public int announceXMana(int min, int max, String message, Game game, Ability ability) { + public int announceX(int min, int max, String message, Game game, Ability source, boolean isManaPay) { if (!canCallFeedback(game)) { return min; } - int xValue = 0; - while (canRespond()) { - prepareForResponse(game); - if (!isExecutingMacro()) { - game.fireGetAmountEvent(playerId, message + CardUtil.getSourceLogName(game, ability), min, max); - } - waitForResponse(game); - - if (response.getInteger() != null) { - break; - } - - // TODO: add response verify here - } - - if (response.getInteger() != null) { - xValue = response.getInteger(); - } - return xValue; - } - - @Override - public int announceXCost(int min, int max, String message, Game game, Ability ability, VariableCost variableCost) { - if (!canCallFeedback(game)) { + // fast calc on nothing to choose + if (min >= max) { return min; } - int xValue = 0; + int xValue = min; while (canRespond()) { prepareForResponse(game); if (!isExecutingMacro()) { - game.fireGetAmountEvent(playerId, message, min, max); + game.fireGetAmountEvent(playerId, message + CardUtil.getSourceLogName(game, source), min, max); } waitForResponse(game); - if (response.getInteger() != null) { - break; + if (response.getInteger() == null) { + continue; } + + xValue = response.getInteger(); + if (xValue < min || xValue > max) { + continue; + } + + break; } - if (response.getInteger() != null) { - xValue = response.getInteger(); - } return xValue; } @@ -2183,28 +2158,37 @@ public class HumanPlayer extends PlayerImpl { } @Override - public int getAmount(int min, int max, String message, Game game) { + public int getAmount(int min, int max, String message, Ability source, Game game) { if (!canCallFeedback(game)) { return min; } + // fast calc on nothing to choose + if (min >= max) { + return min; + } + + int xValue = min; while (canRespond()) { prepareForResponse(game); if (!isExecutingMacro()) { - game.fireGetAmountEvent(playerId, message, min, max); + game.fireGetAmountEvent(playerId, message + CardUtil.getSourceLogName(game, source), min, max); } waitForResponse(game); - if (response.getInteger() != null) { - break; + if (response.getInteger() == null) { + continue; } + + xValue = response.getInteger(); + if (xValue < min || xValue > max) { + continue; + } + + break; } - if (response.getInteger() != null) { - return response.getInteger(); - } else { - return 0; - } + return xValue; } @Override @@ -2624,7 +2608,7 @@ public class HumanPlayer extends PlayerImpl { @Override public boolean choosePile(Outcome outcome, String message, java.util.List pile1, java.util.List pile2, Game game) { if (!canCallFeedback(game)) { - return true; + return false; } while (canRespond()) { diff --git a/Mage.Sets/src/mage/cards/a/ARealmReborn.java b/Mage.Sets/src/mage/cards/a/ARealmReborn.java new file mode 100644 index 00000000000..7ad768a06e6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/ARealmReborn.java @@ -0,0 +1,37 @@ +package mage.cards.a; + +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.mana.AnyColorManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ARealmReborn extends CardImpl { + + public ARealmReborn(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{4}{G}{G}"); + + // Other permanents you control have "{T}: Add one mana of any color." + this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect( + new AnyColorManaAbility(), Duration.WhileOnBattlefield, + StaticFilters.FILTER_PERMANENTS, true + ))); + } + + private ARealmReborn(final ARealmReborn card) { + super(card); + } + + @Override + public ARealmReborn copy() { + return new ARealmReborn(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AbsoluteVirtue.java b/Mage.Sets/src/mage/cards/a/AbsoluteVirtue.java new file mode 100644 index 00000000000..ac78f6fcc7e --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AbsoluteVirtue.java @@ -0,0 +1,85 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.MageItem; +import mage.MageObject; +import mage.abilities.common.CantBeCounteredSourceAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.GainAbilityControllerEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.ProtectionAbility; +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.Game; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AbsoluteVirtue extends CardImpl { + + public AbsoluteVirtue(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{6}{W}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.AVATAR); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(8); + this.toughness = new MageInt(8); + + // This spell can't be countered. + this.addAbility(new CantBeCounteredSourceAbility()); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // You have protection from each of your opponents. + this.addAbility(new SimpleStaticAbility(new GainAbilityControllerEffect(new AbsoluteVirtueAbility()))); + } + + private AbsoluteVirtue(final AbsoluteVirtue card) { + super(card); + } + + @Override + public AbsoluteVirtue copy() { + return new AbsoluteVirtue(this); + } +} + +class AbsoluteVirtueAbility extends ProtectionAbility { + + public AbsoluteVirtueAbility() { + super(StaticFilters.FILTER_CARD); + } + + private AbsoluteVirtueAbility(final AbsoluteVirtueAbility ability) { + super(ability); + } + + @Override + public AbsoluteVirtueAbility copy() { + return new AbsoluteVirtueAbility(this); + } + + @Override + public String getRule() { + return "protection from each of your opponents"; + } + + @Override + public boolean canTarget(MageObject source, Game game) { + return Optional + .ofNullable(source) + .map(MageItem::getId) + .map(game::getControllerId) + .map(uuid -> !game.getOpponents(this.getControllerId()).contains(uuid)) + .orElse(true); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AdventurersInn.java b/Mage.Sets/src/mage/cards/a/AdventurersInn.java new file mode 100644 index 00000000000..30b28f47af6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AdventurersInn.java @@ -0,0 +1,37 @@ +package mage.cards.a; + +import java.util.UUID; +import mage.constants.SubType; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.mana.ColorlessManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; + +/** + * @author balazskristof + */ +public final class AdventurersInn extends CardImpl { + + public AdventurersInn(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + this.subtype.add(SubType.TOWN); + + // When this land enters, you gain 2 life. + this.addAbility(new EntersBattlefieldTriggeredAbility(new GainLifeEffect(2))); + + // {T}: Add {C}. + this.addAbility(new ColorlessManaAbility()); + } + + private AdventurersInn(final AdventurersInn card) { + super(card); + } + + @Override + public AdventurersInn copy() { + return new AdventurersInn(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AerithGainsborough.java b/Mage.Sets/src/mage/cards/a/AerithGainsborough.java new file mode 100644 index 00000000000..b3b0a34250f --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AerithGainsborough.java @@ -0,0 +1,58 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.common.DiesSourceTriggeredAbility; +import mage.abilities.common.GainLifeControllerTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CountersSourceCount; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.LifelinkAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AerithGainsborough extends CardImpl { + + private static final DynamicValue xValue = new CountersSourceCount(CounterType.P1P1); + + public AerithGainsborough(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.CLERIC); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // Whenever you gain life, put a +1/+1 counter on Aerith Gainsborough. + this.addAbility(new GainLifeControllerTriggeredAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance()))); + + // When Aerith Gainsborough dies, put X +1/+1 counters on each legendary creature you control, where X is the number of +1/+1 counters on Aerith Gainsborough. + this.addAbility(new DiesSourceTriggeredAbility(new AddCountersAllEffect( + CounterType.P1P1.createInstance(), xValue, StaticFilters.FILTER_CONTROLLED_CREATURE_LEGENDARY + ).setText("put X +1/+1 counters on each legendary creature you control, " + + "where X is the number of +1/+1 counters on {this}"))); + } + + private AerithGainsborough(final AerithGainsborough card) { + super(card); + } + + @Override + public AerithGainsborough copy() { + return new AerithGainsborough(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AerithLastAncient.java b/Mage.Sets/src/mage/cards/a/AerithLastAncient.java new file mode 100644 index 00000000000..dacb48ef52a --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AerithLastAncient.java @@ -0,0 +1,64 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.YouGainedLifeCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.dynamicvalue.common.ControllerGainedLifeCount; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect; +import mage.abilities.keyword.LifelinkAbility; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.StaticFilters; +import mage.target.common.TargetCardInYourGraveyard; +import mage.watchers.common.PlayerGainedLifeWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AerithLastAncient extends CardImpl { + + private static final Condition condition = new YouGainedLifeCondition(); + private static final Condition condition2 = new YouGainedLifeCondition(ComparisonType.MORE_THAN, 6); + + public AerithLastAncient(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.CLERIC); + this.subtype.add(SubType.DRUID); + this.power = new MageInt(3); + this.toughness = new MageInt(5); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // Raise -- At the beginning of your end step, if you gained life this turn, return target creature card from your graveyard to your hand. If you gained 7 or more life this turn, return that card to the battlefield instead. + Ability ability = new BeginningOfEndStepTriggeredAbility(new ConditionalOneShotEffect( + new ReturnFromGraveyardToBattlefieldTargetEffect(), new ReturnFromGraveyardToHandTargetEffect(), + condition2, "return target creature card from your graveyard to your hand. " + + "If you gained 7 or more life this turn, return that card to the battlefield instead" + )).withInterveningIf(condition); + ability.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_CREATURE_YOUR_GRAVEYARD)); + this.addAbility(ability.withFlavorWord("Raise").addHint(ControllerGainedLifeCount.getHint()), new PlayerGainedLifeWatcher()); + } + + private AerithLastAncient(final AerithLastAncient card) { + super(card); + } + + @Override + public AerithLastAncient copy() { + return new AerithLastAncient(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AetherFigment.java b/Mage.Sets/src/mage/cards/a/AetherFigment.java index e9383fd0a92..e501616cfea 100644 --- a/Mage.Sets/src/mage/cards/a/AetherFigment.java +++ b/Mage.Sets/src/mage/cards/a/AetherFigment.java @@ -1,7 +1,5 @@ - package mage.cards.a; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldAbility; @@ -15,21 +13,20 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.counters.CounterType; +import java.util.UUID; + /** * @author nantuko, BetaSteward_at_googlemail.com */ public final class AetherFigment extends CardImpl { public AetherFigment(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.ILLUSION); this.power = new MageInt(1); this.toughness = new MageInt(1); - // Aether Figment can't be blocked. - this.addAbility(new CantBeBlockedSourceAbility()); - // Kicker {3} this.addAbility(new KickerAbility("{3}")); @@ -40,6 +37,9 @@ public final class AetherFigment extends CardImpl { "If {this} was kicked, it enters with two +1/+1 counters on it.", ""); this.addAbility(ability); + + // Aether Figment can't be blocked. + this.addAbility(new CantBeBlockedSourceAbility()); } private AetherFigment(final AetherFigment card) { diff --git a/Mage.Sets/src/mage/cards/a/AetherRefinery.java b/Mage.Sets/src/mage/cards/a/AetherRefinery.java index 9cfde0c2518..626fc0e4416 100644 --- a/Mage.Sets/src/mage/cards/a/AetherRefinery.java +++ b/Mage.Sets/src/mage/cards/a/AetherRefinery.java @@ -119,7 +119,7 @@ class AetherRefineryTokenEffect extends OneShotEffect { return true; } int numberToPay = controller.getAmount(1, totalEnergy, - "Pay one or more {E}", game); + "Pay one or more {E}", source, game); Cost cost = new PayEnergyCost(numberToPay); if (cost.pay(source, game, source, source.getControllerId(), true)) { diff --git a/Mage.Sets/src/mage/cards/a/AetherRevolt.java b/Mage.Sets/src/mage/cards/a/AetherRevolt.java index b3be8471053..f6f525e1897 100644 --- a/Mage.Sets/src/mage/cards/a/AetherRevolt.java +++ b/Mage.Sets/src/mage/cards/a/AetherRevolt.java @@ -33,7 +33,7 @@ public final class AetherRevolt extends CardImpl { // Revolt -- As long as a permanent you controlled left the battlefield this turn, if a source you control would deal noncombat damage to an opponent or a permanent an opponent controls, it deals that much damage plus 2 instead. this.addAbility(new SimpleStaticAbility(new ConditionalReplacementEffect( new AetherRevoltEffect(), RevoltCondition.instance - ).setText("As long as a permanent you controlled left the battlefield this turn, " + ).setText("as long as a permanent left the battlefield under your control this turn, " + "if a source you control would deal noncombat damage to an opponent or a permanent an opponent controls, " + "it deals that much damage plus 2 instead") ).setAbilityWord(AbilityWord.REVOLT).addHint(RevoltCondition.getHint()), new RevoltWatcher()); diff --git a/Mage.Sets/src/mage/cards/a/AetherSpike.java b/Mage.Sets/src/mage/cards/a/AetherSpike.java index c4866335ea6..37afa6d57b1 100644 --- a/Mage.Sets/src/mage/cards/a/AetherSpike.java +++ b/Mage.Sets/src/mage/cards/a/AetherSpike.java @@ -68,7 +68,7 @@ class AetherSpikeEffect extends OneShotEffect { } int numberToPay = controller.getAmount( 0, controller.getCountersCount(CounterType.ENERGY), - "How many {E} do you want to pay?", game + "How many {E} do you want to pay?", source, game ); Cost cost = new PayEnergyCost(numberToPay); int numberPaid = 0; diff --git a/Mage.Sets/src/mage/cards/a/AetherbornMarauder.java b/Mage.Sets/src/mage/cards/a/AetherbornMarauder.java index 0427c19fc49..f3758b29fe7 100644 --- a/Mage.Sets/src/mage/cards/a/AetherbornMarauder.java +++ b/Mage.Sets/src/mage/cards/a/AetherbornMarauder.java @@ -89,7 +89,7 @@ class AetherbornMarauderEffect extends OneShotEffect { int numberOfCounters = fromPermanent.getCounters(game).getCount(CounterType.P1P1); int numberToMove = 1; if (numberOfCounters > 1) { - numberToMove = controller.getAmount(0, numberOfCounters, "Choose how many +1/+1 counters to move", game); + numberToMove = controller.getAmount(0, numberOfCounters, "Choose how many +1/+1 counters to move", source, game); } if (numberToMove > 0) { fromPermanent.removeCounters(CounterType.P1P1.createInstance(numberToMove), source, game); diff --git a/Mage.Sets/src/mage/cards/a/Aetherspouts.java b/Mage.Sets/src/mage/cards/a/Aetherspouts.java index e2bfdb27cfc..506d81aed8a 100644 --- a/Mage.Sets/src/mage/cards/a/Aetherspouts.java +++ b/Mage.Sets/src/mage/cards/a/Aetherspouts.java @@ -150,7 +150,6 @@ class AetherspoutsEffect extends OneShotEffect { target = new TargetCard(Zone.BATTLEFIELD, new FilterCard("order to put on bottom of library (last chosen will be bottommost card)")); while (player.canRespond() && cards.size() > 1) { player.choose(Outcome.Neutral, cards, target, source, game); - Card card = cards.get(target.getFirstTarget(), game); if (card != null) { cards.remove(card); @@ -158,8 +157,10 @@ class AetherspoutsEffect extends OneShotEffect { if (permanent != null) { toLibrary.add(permanent); } + target.clearChosen(); + } else { + break; } - target.clearChosen(); } if (cards.size() == 1) { Card card = cards.get(cards.iterator().next(), game); diff --git a/Mage.Sets/src/mage/cards/a/AidFromTheCowl.java b/Mage.Sets/src/mage/cards/a/AidFromTheCowl.java index 81879be5107..4fc6fa05bc5 100644 --- a/Mage.Sets/src/mage/cards/a/AidFromTheCowl.java +++ b/Mage.Sets/src/mage/cards/a/AidFromTheCowl.java @@ -1,38 +1,38 @@ package mage.cards.a; -import java.util.UUID; -import mage.MageObject; import mage.abilities.Ability; -import mage.abilities.TriggeredAbility; -import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; import mage.abilities.condition.common.RevoltCondition; -import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.OneShotEffect; -import mage.cards.*; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.CardsImpl; +import mage.constants.AbilityWord; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Zone; -import mage.filter.common.FilterPermanentCard; import mage.game.Game; import mage.players.Player; +import mage.util.CardUtil; import mage.watchers.common.RevoltWatcher; +import java.util.UUID; + /** - * * @author fireshoes */ public final class AidFromTheCowl extends CardImpl { - private static final String ruleText = "Revolt — At the beginning of your end step, if a permanent you controlled left the battlefield this turn, " - + "reveal the top card of your library. If it's a permanent card, you may put it onto the battlefield. Otherwise, you may put it on the bottom of your library."; - public AidFromTheCowl(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{G}{G}"); // Revolt — At the beginning of your end step, if a permanent you controlled left the battlefield this turn, // reveal the top card of your library. If it is a permanent card, you may put it onto the battlefield. Otherwise, put it on the bottom of your library. - TriggeredAbility ability = new BeginningOfEndStepTriggeredAbility(new AidFromTheCowlEffect()); - this.addAbility(new ConditionalInterveningIfTriggeredAbility(ability, RevoltCondition.instance, ruleText).addHint(RevoltCondition.getHint()), new RevoltWatcher()); + this.addAbility(new BeginningOfEndStepTriggeredAbility(new AidFromTheCowlEffect()) + .withInterveningIf(RevoltCondition.instance) + .setAbilityWord(AbilityWord.REVOLT) + .addHint(RevoltCondition.getHint()), new RevoltWatcher()); } private AidFromTheCowl(final AidFromTheCowl card) { @@ -49,7 +49,8 @@ class AidFromTheCowlEffect extends OneShotEffect { AidFromTheCowlEffect() { super(Outcome.PutCreatureInPlay); - this.staticText = "reveal the top card of your library. If it's a permanent card, you may put it onto the battlefield. Otherwise, you may put that card on the bottom of your library"; + this.staticText = "reveal the top card of your library. If it's a permanent card, " + + "you may put it onto the battlefield. Otherwise, you may put that card on the bottom of your library"; } private AidFromTheCowlEffect(final AidFromTheCowlEffect effect) { @@ -64,25 +65,20 @@ class AidFromTheCowlEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - MageObject sourceObject = game.getObject(source); - if (controller == null || sourceObject == null) { + if (controller == null || !controller.getLibrary().hasCards()) { return false; } - - if (controller.getLibrary().hasCards()) { - Card card = controller.getLibrary().getFromTop(game); - Cards cards = new CardsImpl(card); - controller.revealCards(sourceObject.getIdName(), cards, game); - - if (card != null) { - if (new FilterPermanentCard().match(card, game) && controller.chooseUse(Outcome.Neutral, "Put " + card.getIdName() + " onto the battlefield?", source, game)) { - controller.moveCards(card, Zone.BATTLEFIELD, source, game); - } else if (controller.chooseUse(Outcome.Neutral, "Put " + card.getIdName() + " on the bottom of your library?", source, game)) { - controller.putCardsOnBottomOfLibrary(cards, game, source, false); - } else { - game.informPlayers(controller.getLogName() + " puts the revealed card back to the top of the library."); - } - } + Card card = controller.getLibrary().getFromTop(game); + if (card == null) { + return false; + } + controller.revealCards(CardUtil.getSourceIdName(game, source), new CardsImpl(card), game); + if (card.isPermanent(game) && controller.chooseUse(Outcome.Neutral, "Put " + card.getIdName() + " onto the battlefield?", source, game)) { + controller.moveCards(card, Zone.BATTLEFIELD, source, game); + } else if (controller.chooseUse(Outcome.Neutral, "Put " + card.getIdName() + " on the bottom of your library?", source, game)) { + controller.putCardsOnBottomOfLibrary(card, game, source, false); + } else { + game.informPlayers(controller.getLogName() + " puts the revealed card back to the top of the library."); } return true; } diff --git a/Mage.Sets/src/mage/cards/a/AirdropAeronauts.java b/Mage.Sets/src/mage/cards/a/AirdropAeronauts.java index 15687ad96e3..0d870248803 100644 --- a/Mage.Sets/src/mage/cards/a/AirdropAeronauts.java +++ b/Mage.Sets/src/mage/cards/a/AirdropAeronauts.java @@ -1,10 +1,8 @@ package mage.cards.a; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.condition.common.RevoltCondition; -import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; @@ -33,13 +31,10 @@ public final class AirdropAeronauts extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // Revolt — When Airdrop Aeronauts enters the battlefield, if a permanent you controlled left the battlefield this turn, you gain 5 life. - Ability ability = new ConditionalInterveningIfTriggeredAbility( - new EntersBattlefieldTriggeredAbility(new GainLifeEffect(5), false), - RevoltCondition.instance, "When {this} enters, " + - "if a permanent you controlled left the battlefield this turn, you gain 5 life." - ); - ability.setAbilityWord(AbilityWord.REVOLT); - this.addAbility(ability.addHint(RevoltCondition.getHint()), new RevoltWatcher()); + this.addAbility(new EntersBattlefieldTriggeredAbility(new GainLifeEffect(5)) + .withInterveningIf(RevoltCondition.instance) + .setAbilityWord(AbilityWord.REVOLT) + .addHint(RevoltCondition.getHint()), new RevoltWatcher()); } private AirdropAeronauts(final AirdropAeronauts card) { diff --git a/Mage.Sets/src/mage/cards/a/AlBhedSalvagers.java b/Mage.Sets/src/mage/cards/a/AlBhedSalvagers.java new file mode 100644 index 00000000000..c07fd1db887 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AlBhedSalvagers.java @@ -0,0 +1,49 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DiesThisOrAnotherTriggeredAbility; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.LoseLifeTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AlBhedSalvagers extends CardImpl { + + public AlBhedSalvagers(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ARTIFICER); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Whenever this creature or another creature or artifact you control dies, target opponent loses 1 life and you gain 1 life. + Ability ability = new DiesThisOrAnotherTriggeredAbility( + new LoseLifeTargetEffect(1), false, + StaticFilters.FILTER_CONTROLLED_ANOTHER_CREATURE_OR_ARTIFACT + ); + ability.addEffect(new GainLifeEffect(1).concatBy("and")); + ability.addTarget(new TargetOpponent()); + this.addAbility(ability); + } + + private AlBhedSalvagers(final AlBhedSalvagers card) { + super(card); + } + + @Override + public AlBhedSalvagers copy() { + return new AlBhedSalvagers(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AlignedHeart.java b/Mage.Sets/src/mage/cards/a/AlignedHeart.java index f031fd1ead0..73104b645a2 100644 --- a/Mage.Sets/src/mage/cards/a/AlignedHeart.java +++ b/Mage.Sets/src/mage/cards/a/AlignedHeart.java @@ -27,7 +27,7 @@ public final class AlignedHeart extends CardImpl { // Flurry -- Whenever you cast your second spell each turn, put a rally counter on this enchantment. Then create a 1/1 white Monk creature token with prowess for each rally counter on it. Ability ability = new FlurryAbility(new AddCountersSourceEffect(CounterType.RALLY.createInstance())); ability.addEffect(new CreateTokenEffect(new MonasteryMentorToken(), xValue) - .setText("then create a 1/1 white Monk creature token with prowess for each rally counter on it")); + .setText("Then create a 1/1 white Monk creature token with prowess for each rally counter on it")); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/a/AlisaieLeveilleur.java b/Mage.Sets/src/mage/cards/a/AlisaieLeveilleur.java new file mode 100644 index 00000000000..cad2c405bd2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AlisaieLeveilleur.java @@ -0,0 +1,54 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.YouCastExactOneSpellThisTurnCondition; +import mage.abilities.decorator.ConditionalCostModificationEffect; +import mage.abilities.effects.common.cost.SpellsCostReductionControllerEffect; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.abilities.keyword.PartnerWithAbility; +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 AlisaieLeveilleur extends CardImpl { + + public AlisaieLeveilleur(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.ELF); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Partner with Alphinaud Leveilleur + this.addAbility(new PartnerWithAbility("Alphinaud Leveilleur")); + + // First strike + this.addAbility(FirstStrikeAbility.getInstance()); + + // Dualcast -- The second spell you cast each turn costs {2} less to cast. + this.addAbility(new SimpleStaticAbility(new ConditionalCostModificationEffect( + new SpellsCostReductionControllerEffect(StaticFilters.FILTER_CARD, 2), + YouCastExactOneSpellThisTurnCondition.instance, "the second spell you cast each turn costs {2} less to cast" + )).withFlavorWord("Dualcast")); + } + + private AlisaieLeveilleur(final AlisaieLeveilleur card) { + super(card); + } + + @Override + public AlisaieLeveilleur copy() { + return new AlisaieLeveilleur(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AlmostPerfect.java b/Mage.Sets/src/mage/cards/a/AlmostPerfect.java index 485ba555181..b03fda0e99d 100644 --- a/Mage.Sets/src/mage/cards/a/AlmostPerfect.java +++ b/Mage.Sets/src/mage/cards/a/AlmostPerfect.java @@ -4,7 +4,7 @@ import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.AttachEffect; import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; -import mage.abilities.effects.common.continuous.SetBasePowerToughnessEnchantedEffect; +import mage.abilities.effects.common.continuous.SetBasePowerToughnessAttachedEffect; import mage.abilities.keyword.EnchantAbility; import mage.abilities.keyword.IndestructibleAbility; import mage.cards.CardImpl; @@ -35,7 +35,7 @@ public final class AlmostPerfect extends CardImpl { this.addAbility(new EnchantAbility(auraTarget)); // Enchanted creature has base power and toughness 9/10 and has indestructible. - Ability ability = new SimpleStaticAbility(new SetBasePowerToughnessEnchantedEffect(9, 10)); + Ability ability = new SimpleStaticAbility(new SetBasePowerToughnessAttachedEffect(9, 10, AttachmentType.AURA)); ability.addEffect(new GainAbilityAttachedEffect( IndestructibleAbility.getInstance(), AttachmentType.AURA ).setText("and has indestructible")); diff --git a/Mage.Sets/src/mage/cards/a/AlphinaudLeveilleur.java b/Mage.Sets/src/mage/cards/a/AlphinaudLeveilleur.java new file mode 100644 index 00000000000..fdfa8602688 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AlphinaudLeveilleur.java @@ -0,0 +1,48 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.common.CastSecondSpellTriggeredAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.keyword.PartnerWithAbility; +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 java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AlphinaudLeveilleur extends CardImpl { + + public AlphinaudLeveilleur(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.ELF); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // Partner with Alisaie Leveilleur + this.addAbility(new PartnerWithAbility("Alisaie Leveilleur")); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Eukrasia -- Whenever you cast your second spell each turn, draw a card. + this.addAbility(new CastSecondSpellTriggeredAbility(new DrawCardSourceControllerEffect(1)).withFlavorWord("Eukrasia")); + } + + private AlphinaudLeveilleur(final AlphinaudLeveilleur card) { + super(card); + } + + @Override + public AlphinaudLeveilleur copy() { + return new AlphinaudLeveilleur(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AltairIbnLaAhad.java b/Mage.Sets/src/mage/cards/a/AltairIbnLaAhad.java new file mode 100644 index 00000000000..cd64eedaa1b --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AltairIbnLaAhad.java @@ -0,0 +1,144 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.delayed.AtTheEndOfCombatDelayedTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenCopyTargetEffect; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.keyword.FirstStrikeAbility; +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.common.FilterCreatureCard; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.PermanentCard; +import mage.players.Player; +import mage.target.common.TargetCardInYourGraveyard; +import mage.target.targetpointer.FixedTargets; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * @author TheElk801 + */ +public final class AltairIbnLaAhad extends CardImpl { + + private static final FilterCard filter = new FilterCreatureCard("Assassin creature card from your graveyard"); + + static { + filter.add(SubType.ASSASSIN.getPredicate()); + } + + public AltairIbnLaAhad(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{R}{W}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ASSASSIN); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // First strike + this.addAbility(FirstStrikeAbility.getInstance()); + + // Whenever Altair Ibn-La'Ahad attacks, exile up to one target Assassin creature card from your graveyard with a memory counter on it. Then for each creature card you own in exile with a memory counter on it, create a tapped and attacking token that's a copy of it. Exile those tokens at end of combat. + Ability ability = new AttacksTriggeredAbility(new AltairIbnLaAhadExileEffect()); + ability.addEffect(new AltairIbnLaAhadTokenEffect()); + ability.addTarget(new TargetCardInYourGraveyard(0, 1, filter)); + this.addAbility(ability); + } + + private AltairIbnLaAhad(final AltairIbnLaAhad card) { + super(card); + } + + @Override + public AltairIbnLaAhad copy() { + return new AltairIbnLaAhad(this); + } +} + +class AltairIbnLaAhadExileEffect extends OneShotEffect { + + AltairIbnLaAhadExileEffect() { + super(Outcome.Benefit); + staticText = "exile up to one target Assassin creature card from your graveyard with a memory counter on it"; + } + + private AltairIbnLaAhadExileEffect(final AltairIbnLaAhadExileEffect effect) { + super(effect); + } + + @Override + public AltairIbnLaAhadExileEffect copy() { + return new AltairIbnLaAhadExileEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); + if (player == null || card == null) { + return false; + } + player.moveCards(card, Zone.EXILED, source, game); + card.addCounters(CounterType.MEMORY.createInstance(), source, game); + return true; + } +} + +class AltairIbnLaAhadTokenEffect extends OneShotEffect { + + AltairIbnLaAhadTokenEffect() { + super(Outcome.Benefit); + staticText = "Then for each creature card you own in exile with a memory counter on it, " + + "create a tapped and attacking token that's a copy of it. Exile those tokens at end of combat"; + } + + private AltairIbnLaAhadTokenEffect(final AltairIbnLaAhadTokenEffect effect) { + super(effect); + } + + @Override + public AltairIbnLaAhadTokenEffect copy() { + return new AltairIbnLaAhadTokenEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Set cards = game + .getExile() + .getAllCards(game, source.getControllerId()) + .stream() + .filter(card -> card.getCounters(game).containsKey(CounterType.MEMORY)) + .filter(card -> card.isCreature(game)) + .collect(Collectors.toSet()); + if (cards.isEmpty()) { + return false; + } + Set permanents = new HashSet<>(); + for (Card card : cards) { + CreateTokenCopyTargetEffect effect = new CreateTokenCopyTargetEffect( + source.getControllerId(), null, + false, 1, true, true + ); + effect.setSavedPermanent(new PermanentCard(card, source.getControllerId(), game)); + effect.apply(game, source); + permanents.addAll(effect.getAddedPermanents()); + } + game.addDelayedTriggeredAbility(new AtTheEndOfCombatDelayedTriggeredAbility( + new ExileTargetEffect("exile those tokens") + .setTargetPointer(new FixedTargets(permanents, game)) + ), source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/a/AnafenzaUnyieldingLineage.java b/Mage.Sets/src/mage/cards/a/AnafenzaUnyieldingLineage.java index fb921218f6d..181374897ac 100644 --- a/Mage.Sets/src/mage/cards/a/AnafenzaUnyieldingLineage.java +++ b/Mage.Sets/src/mage/cards/a/AnafenzaUnyieldingLineage.java @@ -47,7 +47,7 @@ public final class AnafenzaUnyieldingLineage extends CardImpl { // Whenever another nontoken creature you control dies, Anafenza endures 2. this.addAbility(new DiesCreatureTriggeredAbility( - new EndureSourceEffect(2, "{this}"), true, filter + new EndureSourceEffect(2, "{this}"), false, filter )); } diff --git a/Mage.Sets/src/mage/cards/a/AncientCellarspawn.java b/Mage.Sets/src/mage/cards/a/AncientCellarspawn.java new file mode 100644 index 00000000000..41376a14b31 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AncientCellarspawn.java @@ -0,0 +1,113 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.LoseLifeTargetEffect; +import mage.abilities.effects.common.cost.SpellsCostReductionControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.FilterSpell; +import mage.filter.predicate.Predicate; +import mage.filter.predicate.Predicates; +import mage.game.Game; +import mage.game.stack.Spell; +import mage.game.stack.StackObject; +import mage.target.common.TargetOpponent; +import mage.watchers.common.ManaPaidSourceWatcher; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AncientCellarspawn extends CardImpl { + + private static final FilterCard filter + = new FilterCard("each spell you cast that's a Demon, Horror, or Nightmare"); + private static final FilterSpell filter2 + = new FilterSpell("a spell, if the amount of mana spent to cast it was less than its mana value"); + + static { + filter.add(Predicates.or( + SubType.DEMON.getPredicate(), + SubType.HORROR.getPredicate(), + SubType.NIGHTMARE.getPredicate() + )); + filter2.add(AncientCellarspawnPredicate.instance); + } + + public AncientCellarspawn(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, "{1}{B}{B}"); + + this.subtype.add(SubType.HORROR); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Each spell you cast that's a Demon, Horror, or Nightmare costs {1} less to cast. + this.addAbility(new SimpleStaticAbility(new SpellsCostReductionControllerEffect(filter, 1))); + + // Whenever you cast a spell, if the amount of mana spent to cast it was less than its mana value, target opponent loses life equal to the difference. + Ability ability = new SpellCastControllerTriggeredAbility( + new LoseLifeTargetEffect(AncientCellarspawnValue.instance) + .setText("target opponent loses life equal to the difference"), + filter2, false + ); + ability.addTarget(new TargetOpponent()); + this.addAbility(ability); + } + + private AncientCellarspawn(final AncientCellarspawn card) { + super(card); + } + + @Override + public AncientCellarspawn copy() { + return new AncientCellarspawn(this); + } +} + +enum AncientCellarspawnPredicate implements Predicate { + instance; + + @Override + public boolean apply(StackObject input, Game game) { + return ManaPaidSourceWatcher.getTotalPaid(input.getSourceId(), game) < input.getManaValue(); + } +} + +enum AncientCellarspawnValue implements DynamicValue { + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return Optional + .ofNullable(effect.getValue("spellCast")) + .filter(Spell.class::isInstance) + .map(Spell.class::cast) + .map(spell -> Math.abs(spell.getManaValue() - ManaPaidSourceWatcher.getTotalPaid(spell.getId(), game))) + .orElse(0); + } + + @Override + public AncientCellarspawnValue copy() { + return this; + } + + @Override + public String getMessage() { + return ""; + } + + @Override + public String toString() { + return "1"; + } +} diff --git a/Mage.Sets/src/mage/cards/a/AngelicObserver.java b/Mage.Sets/src/mage/cards/a/AngelicObserver.java index b2722037366..9829ad683ef 100644 --- a/Mage.Sets/src/mage/cards/a/AngelicObserver.java +++ b/Mage.Sets/src/mage/cards/a/AngelicObserver.java @@ -2,9 +2,8 @@ package mage.cards.a; import mage.MageInt; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; -import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; +import mage.abilities.effects.common.AffinityEffect; import mage.abilities.hint.Hint; import mage.abilities.hint.ValueHint; import mage.abilities.keyword.FlyingAbility; @@ -13,7 +12,6 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.FilterPermanent; import mage.filter.common.FilterControlledPermanent; import java.util.UUID; @@ -23,10 +21,8 @@ import java.util.UUID; */ public final class AngelicObserver extends CardImpl { - private static final FilterPermanent filter - = new FilterControlledPermanent(SubType.CITIZEN, "Citizen you control"); - private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter); - private static final Hint hint = new ValueHint("Citizens you control", xValue); + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.CITIZEN, "Citizens"); + private static final Hint hint = new ValueHint("Citizens you control", new PermanentsOnBattlefieldCount(filter)); public AngelicObserver(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{W}"); @@ -37,10 +33,7 @@ public final class AngelicObserver extends CardImpl { this.toughness = new MageInt(3); // This spell costs {1} less to cast for each Citizen you control. - this.addAbility(new SimpleStaticAbility( - Zone.ALL, - new SpellCostReductionForEachSourceEffect(1, xValue).setCanWorksOnStackOnly(true) - ).setRuleAtTheTop(true).addHint(hint)); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new AffinityEffect(filter)).setRuleAtTheTop(true).addHint(hint)); // Flying this.addAbility(FlyingAbility.getInstance()); diff --git a/Mage.Sets/src/mage/cards/a/ArashinSunshield.java b/Mage.Sets/src/mage/cards/a/ArashinSunshield.java index 3ea6618a2aa..f99069039d0 100644 --- a/Mage.Sets/src/mage/cards/a/ArashinSunshield.java +++ b/Mage.Sets/src/mage/cards/a/ArashinSunshield.java @@ -33,7 +33,7 @@ public final class ArashinSunshield extends CardImpl { // When this creature enters, exile up to two target cards from a single graveyard. Ability ability = new EntersBattlefieldTriggeredAbility(new ExileTargetEffect()); - ability.addTarget(new TargetCardInASingleGraveyard(0, 2, StaticFilters.FILTER_CARDS_NON_LAND)); + ability.addTarget(new TargetCardInASingleGraveyard(0, 2, StaticFilters.FILTER_CARD_CARDS)); this.addAbility(ability); // {W}, {T}: Tap target creature. diff --git a/Mage.Sets/src/mage/cards/a/ArchdruidsCharm.java b/Mage.Sets/src/mage/cards/a/ArchdruidsCharm.java index a50c5b90541..7f9b0caa593 100644 --- a/Mage.Sets/src/mage/cards/a/ArchdruidsCharm.java +++ b/Mage.Sets/src/mage/cards/a/ArchdruidsCharm.java @@ -137,7 +137,7 @@ class ArchdruidsCharmMode1Effect extends SearchEffect { private void setText() { StringBuilder sb = new StringBuilder(); sb.append("Search your library for "); - if (target.getNumberOfTargets() == 0 && target.getMaxNumberOfTargets() > 0) { + if (target.getMinNumberOfTargets() == 0 && target.getMaxNumberOfTargets() > 0) { sb.append("up to ").append(CardUtil.numberToText(target.getMaxNumberOfTargets())).append(' '); sb.append(target.getTargetName()).append(revealCards ? " and reveal them." : "."); } else { diff --git a/Mage.Sets/src/mage/cards/a/ArdbertWarriorOfDarkness.java b/Mage.Sets/src/mage/cards/a/ArdbertWarriorOfDarkness.java new file mode 100644 index 00000000000..8c5771c70f1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/ArdbertWarriorOfDarkness.java @@ -0,0 +1,75 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.common.continuous.GainAbilityAllEffect; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.abilities.keyword.MenaceAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.FilterSpell; +import mage.filter.StaticFilters; +import mage.filter.predicate.mageobject.ColorPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ArdbertWarriorOfDarkness extends CardImpl { + + private static final FilterSpell filter = new FilterSpell("a white spell"); + private static final FilterSpell filter2 = new FilterSpell("a black spell"); + + static { + filter.add(new ColorPredicate(ObjectColor.WHITE)); + filter2.add(new ColorPredicate(ObjectColor.BLACK)); + } + + public ArdbertWarriorOfDarkness(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPIRIT); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Whenever you cast a white spell, put a +1/+1 counter on each legendary creature you control. They gain vigilance until end of turn. + Ability ability = new SpellCastControllerTriggeredAbility(new AddCountersAllEffect( + CounterType.P1P1.createInstance(), StaticFilters.FILTER_CONTROLLED_CREATURE_LEGENDARY + ), filter, false); + ability.addEffect(new GainAbilityAllEffect( + VigilanceAbility.getInstance(), Duration.EndOfTurn, + StaticFilters.FILTER_CONTROLLED_CREATURE_LEGENDARY + ).setText("They gain vigilance until end of turn")); + this.addAbility(ability); + + // Whenever you cast a black spell, put a +1/+1 counter on each legendary creature you control. They gain menace until end of turn. + ability = new SpellCastControllerTriggeredAbility(new AddCountersAllEffect( + CounterType.P1P1.createInstance(), StaticFilters.FILTER_CONTROLLED_CREATURE_LEGENDARY + ), filter2, false); + ability.addEffect(new GainAbilityAllEffect( + new MenaceAbility(false), Duration.EndOfTurn, + StaticFilters.FILTER_CONTROLLED_CREATURE_LEGENDARY + ).setText("They gain menace until end of turn")); + this.addAbility(ability); + } + + private ArdbertWarriorOfDarkness(final ArdbertWarriorOfDarkness card) { + super(card); + } + + @Override + public ArdbertWarriorOfDarkness copy() { + return new ArdbertWarriorOfDarkness(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/ArdynTheUsurper.java b/Mage.Sets/src/mage/cards/a/ArdynTheUsurper.java new file mode 100644 index 00000000000..e0f20d5da57 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/ArdynTheUsurper.java @@ -0,0 +1,107 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenCopyTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.LifelinkAbility; +import mage.abilities.keyword.MenaceAbility; +import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCardInGraveyard; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ArdynTheUsurper extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent(SubType.DEMON, "Demons"); + + public ArdynTheUsurper(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{B}{B}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.ELDER); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.NOBLE); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Demons you control have menace, lifelink, and haste. + Ability ability = new SimpleStaticAbility(new GainAbilityControlledEffect( + new MenaceAbility(false), Duration.WhileOnBattlefield, filter + )); + ability.addEffect(new GainAbilityControlledEffect( + LifelinkAbility.getInstance(), Duration.WhileOnBattlefield, filter + ).setText(", lifelink")); + ability.addEffect(new GainAbilityControlledEffect( + HasteAbility.getInstance(), Duration.WhileOnBattlefield, filter + ).setText(", and haste")); + this.addAbility(ability); + + // Starscourge -- At the beginning of combat on your turn, exile up to one target creature card from a graveyard. If you exiled a card this way, create a token that's a copy of that card, except it's a 5/5 black Demon. + ability = new BeginningOfCombatTriggeredAbility(new ArdynTheUsurperEffect()); + ability.addTarget(new TargetCardInGraveyard( + 0, 1, StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD + )); + this.addAbility(ability.withFlavorWord("Starscourge")); + } + + private ArdynTheUsurper(final ArdynTheUsurper card) { + super(card); + } + + @Override + public ArdynTheUsurper copy() { + return new ArdynTheUsurper(this); + } +} + +class ArdynTheUsurperEffect extends OneShotEffect { + + ArdynTheUsurperEffect() { + super(Outcome.Benefit); + staticText = "at the beginning of combat on your turn, exile up to one target creature card from a graveyard. " + + "If you exiled a card this way, create a token that's a copy of that card, except it's a 5/5 black Demon"; + } + + private ArdynTheUsurperEffect(final ArdynTheUsurperEffect effect) { + super(effect); + } + + @Override + public ArdynTheUsurperEffect copy() { + return new ArdynTheUsurperEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); + if (player == null || card == null) { + return false; + } + player.moveCards(card, Zone.EXILED, source, game); + return new CreateTokenCopyTargetEffect( + null, null, false, 1, false, + false, null, 5, 5, false + ).setOnlyColor(ObjectColor.BLACK) + .setOnlySubType(SubType.DEMON) + .setTargetPointer(new FixedTarget(card, game)) + .apply(game, source); + } +} diff --git a/Mage.Sets/src/mage/cards/a/ArgivianPhalanx.java b/Mage.Sets/src/mage/cards/a/ArgivianPhalanx.java index 1319959a027..f8c6e515b47 100644 --- a/Mage.Sets/src/mage/cards/a/ArgivianPhalanx.java +++ b/Mage.Sets/src/mage/cards/a/ArgivianPhalanx.java @@ -2,8 +2,7 @@ package mage.cards.a; import mage.MageInt; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.dynamicvalue.common.CreaturesYouControlCount; -import mage.abilities.effects.common.cost.SpellCostReductionSourceEffect; +import mage.abilities.effects.common.AffinityEffect; import mage.abilities.hint.common.CreaturesYouControlHint; import mage.abilities.keyword.VigilanceAbility; import mage.cards.CardImpl; @@ -11,6 +10,8 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; import java.util.UUID; @@ -19,6 +20,8 @@ import java.util.UUID; */ public final class ArgivianPhalanx extends CardImpl { + static final FilterControlledPermanent filter = new FilterControlledCreaturePermanent("creatures"); + public ArgivianPhalanx(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{W}"); @@ -29,11 +32,7 @@ public final class ArgivianPhalanx extends CardImpl { this.toughness = new MageInt(4); // This spell costs {1} less to cast for each creature you control. - this.addAbility(new SimpleStaticAbility( - Zone.ALL, - new SpellCostReductionSourceEffect(CreaturesYouControlCount.instance) - .setText("This spell costs {1} less to cast for each creature you control.") - ).addHint(CreaturesYouControlHint.instance)); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new AffinityEffect(filter)).addHint(CreaturesYouControlHint.instance)); // Vigilance this.addAbility(VigilanceAbility.getInstance()); diff --git a/Mage.Sets/src/mage/cards/a/ArmamentDragon.java b/Mage.Sets/src/mage/cards/a/ArmamentDragon.java index db243795b2e..0f401af606a 100644 --- a/Mage.Sets/src/mage/cards/a/ArmamentDragon.java +++ b/Mage.Sets/src/mage/cards/a/ArmamentDragon.java @@ -10,7 +10,8 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.counters.CounterType; -import mage.target.common.TargetCreaturePermanentAmount; +import mage.filter.StaticFilters; +import mage.target.common.TargetPermanentAmount; import java.util.UUID; @@ -31,7 +32,7 @@ public final class ArmamentDragon extends CardImpl { // When this creature enters, distribute three +1/+1 counters among one, two, or three target creatures you control. Ability ability = new EntersBattlefieldTriggeredAbility(new DistributeCountersEffect(CounterType.P1P1)); - ability.addTarget(new TargetCreaturePermanentAmount(3)); + ability.addTarget(new TargetPermanentAmount(3, 1, StaticFilters.FILTER_CONTROLLED_CREATURES)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/a/AuronVeneratedGuardian.java b/Mage.Sets/src/mage/cards/a/AuronVeneratedGuardian.java new file mode 100644 index 00000000000..d3edf3ab57f --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AuronVeneratedGuardian.java @@ -0,0 +1,86 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.costs.common.PutCountersSourceCost; +import mage.abilities.effects.common.DoWhenCostPaid; +import mage.abilities.effects.common.ExileUntilSourceLeavesEffect; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.filter.predicate.permanent.DefendingPlayerControlsSourceAttackingPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AuronVeneratedGuardian extends CardImpl { + + private static final FilterPermanent filter + = new FilterCreaturePermanent("creature defending player controls with power less than {this}'s power"); + + static { + filter.add(DefendingPlayerControlsSourceAttackingPredicate.instance); + filter.add(AuronVeneratedGuardianPredicate.instance); + } + + public AuronVeneratedGuardian(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SPIRIT); + this.subtype.add(SubType.SAMURAI); + this.power = new MageInt(2); + this.toughness = new MageInt(5); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Shooting Star -- Whenever Auron attacks, put a +1/+1 counter on it. When you do, exile target creature defending player controls with power less than Auron's power until Auron leaves the battlefield. + ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility(new ExileUntilSourceLeavesEffect(), false); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(new AttacksTriggeredAbility(new DoWhenCostPaid( + ability, new PutCountersSourceCost(CounterType.P1P1.createInstance()) + .setText("put a +1/+1 counter on it"), "", false + )).withFlavorWord("Shooting Star")); + } + + private AuronVeneratedGuardian(final AuronVeneratedGuardian card) { + super(card); + } + + @Override + public AuronVeneratedGuardian copy() { + return new AuronVeneratedGuardian(this); + } +} + +enum AuronVeneratedGuardianPredicate implements ObjectSourcePlayerPredicate { + instance; + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + return Optional + .ofNullable(input.getSource().getSourcePermanentOrLKI(game)) + .map(MageObject::getPower) + .map(MageInt::getValue) + .map(x -> input.getObject().getPower().getValue() < x) + .orElse(false); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AuronsInspiration.java b/Mage.Sets/src/mage/cards/a/AuronsInspiration.java new file mode 100644 index 00000000000..5e17f1d335a --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AuronsInspiration.java @@ -0,0 +1,40 @@ +package mage.cards.a; + +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.continuous.BoostAllEffect; +import mage.abilities.keyword.FlashbackAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AuronsInspiration extends CardImpl { + + public AuronsInspiration(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{W}"); + + // Attacking creatures get +2/+0 until end of turn. + this.getSpellAbility().addEffect(new BoostAllEffect( + 2, 0, Duration.EndOfTurn, + StaticFilters.FILTER_ATTACKING_CREATURES, false + )); + + // Flashback {2}{W}{W} + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{2}{W}{W}"))); + } + + private AuronsInspiration(final AuronsInspiration card) { + super(card); + } + + @Override + public AuronsInspiration copy() { + return new AuronsInspiration(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AutonSoldier.java b/Mage.Sets/src/mage/cards/a/AutonSoldier.java index 2292a460b4c..48207829cce 100644 --- a/Mage.Sets/src/mage/cards/a/AutonSoldier.java +++ b/Mage.Sets/src/mage/cards/a/AutonSoldier.java @@ -47,7 +47,7 @@ public final class AutonSoldier extends CardImpl { Zone.ALL, new EntersBattlefieldEffect(new CopyPermanentEffect( StaticFilters.FILTER_PERMANENT_CREATURE, applier - ).setText("You may have {this} enter the battlefield as a copy of any creature on the battlefield, " + + ).setText("You may have {this} enter as a copy of any creature on the battlefield, " + "except it isn't legendary, is an artifact in addition to its other types, and has myriad"), "", true)) ); } diff --git a/Mage.Sets/src/mage/cards/a/AvalancheOfSector7.java b/Mage.Sets/src/mage/cards/a/AvalancheOfSector7.java new file mode 100644 index 00000000000..6ef053cc5c2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AvalancheOfSector7.java @@ -0,0 +1,98 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.continuous.SetBasePowerSourceEffect; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterArtifactPermanent; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AvalancheOfSector7 extends CardImpl { + + private static final FilterPermanent filter = new FilterArtifactPermanent("artifacts your opponents control"); + + static { + filter.add(TargetController.OPPONENT.getControllerPredicate()); + } + + private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter); + + public AvalancheOfSector7(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.REBEL); + this.power = new MageInt(0); + this.toughness = new MageInt(3); + + // Menace + this.addAbility(new MenaceAbility()); + + // Avalanche of Sector 7's power is equal to the number of artifacts your opponents control. + this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerSourceEffect(xValue))); + + // Whenever an opponent activates an ability of an artifact they control, Avalanche of Sector 7 deals 1 damage to that player. + this.addAbility(new AvalancheOfSector7TriggeredAbility()); + } + + private AvalancheOfSector7(final AvalancheOfSector7 card) { + super(card); + } + + @Override + public AvalancheOfSector7 copy() { + return new AvalancheOfSector7(this); + } +} + +class AvalancheOfSector7TriggeredAbility extends TriggeredAbilityImpl { + + AvalancheOfSector7TriggeredAbility() { + super(Zone.BATTLEFIELD, new DamageTargetEffect(1, true, "that player", true)); + setTriggerPhrase("Whenever an opponent activates an ability of an artifact they control, "); + } + + private AvalancheOfSector7TriggeredAbility(final AvalancheOfSector7TriggeredAbility ability) { + super(ability); + } + + @Override + public AvalancheOfSector7TriggeredAbility copy() { + return new AvalancheOfSector7TriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ACTIVATED_ABILITY; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (!game.getOpponents(getControllerId()).contains(event.getPlayerId())) { + return false; + } + Permanent permanent = game.getPermanentOrLKIBattlefield(event.getSourceId()); + if (permanent == null || !permanent.isArtifact(game) || !permanent.isControlledBy(event.getPlayerId())) { + return false; + } + this.getEffects().setTargetPointer(new FixedTarget(event.getPlayerId())); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/a/AxebaneGuardian.java b/Mage.Sets/src/mage/cards/a/AxebaneGuardian.java index c074b1858d4..052fcaf3a62 100644 --- a/Mage.Sets/src/mage/cards/a/AxebaneGuardian.java +++ b/Mage.Sets/src/mage/cards/a/AxebaneGuardian.java @@ -1,10 +1,11 @@ - package mage.cards.a; -import java.util.UUID; import mage.MageInt; import mage.Mana; +import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; import mage.abilities.keyword.DefenderAbility; import mage.abilities.mana.DynamicManaAbility; import mage.cards.CardImpl; @@ -15,20 +16,24 @@ import mage.filter.FilterPermanent; import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.predicate.mageobject.AbilityPredicate; +import java.util.UUID; + /** - * * @author Plopman */ public final class AxebaneGuardian extends CardImpl { - private static final FilterPermanent filter = new FilterControlledCreaturePermanent("creatures with defender you control"); + private static final FilterPermanent filter = new FilterControlledCreaturePermanent(); - static{ + static { filter.add(new AbilityPredicate(DefenderAbility.class)); } + private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter); + private static final Hint hint = new ValueHint("Creatures you control with defender", xValue); + public AxebaneGuardian(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); this.subtype.add(SubType.HUMAN); this.subtype.add(SubType.DRUID); @@ -39,8 +44,10 @@ public final class AxebaneGuardian extends CardImpl { this.addAbility(DefenderAbility.getInstance()); // {tap}: Add X mana in any combination of colors, where X is the number of creatures with defender you control. - this.addAbility(new DynamicManaAbility(new Mana(0, 0, 0, 0,0, 0,1, 0), new PermanentsOnBattlefieldCount(filter), - "Add X mana in any combination of colors, where X is the number of creatures with defender you control.")); + this.addAbility(new DynamicManaAbility( + Mana.AnyMana(1), xValue, "Add X mana in any combination of colors, " + + "where X is the number of creatures you control with defender." + ).addHint(hint)); } private AxebaneGuardian(final AxebaneGuardian card) { diff --git a/Mage.Sets/src/mage/cards/a/AzorTheLawbringer.java b/Mage.Sets/src/mage/cards/a/AzorTheLawbringer.java index 6159e195bbe..82467e4f9b8 100644 --- a/Mage.Sets/src/mage/cards/a/AzorTheLawbringer.java +++ b/Mage.Sets/src/mage/cards/a/AzorTheLawbringer.java @@ -163,7 +163,7 @@ class AzorTheLawbringerAttacksEffect extends OneShotEffect { if (controller != null) { ManaCosts cost = new ManaCostsImpl<>("{X}{W}{U}{U}"); if (controller.chooseUse(Outcome.Damage, "Pay " + cost.getText() + "? If you do, you gain X life and draw X cards.", source, game)) { - int costX = controller.announceXMana(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source); + int costX = controller.announceX(0, Integer.MAX_VALUE, "Announce the value for {X} (gain life and draw cards)", game, source,true); cost.add(new GenericManaCost(costX)); if (cost.pay(source, game, source, source.getControllerId(), false, null)) { controller.resetStoredBookmark(game); // otherwise you can undo the payment diff --git a/Mage.Sets/src/mage/cards/b/BalduvianWarlord.java b/Mage.Sets/src/mage/cards/b/BalduvianWarlord.java index 55a1fcc34b2..43420c8bd1a 100644 --- a/Mage.Sets/src/mage/cards/b/BalduvianWarlord.java +++ b/Mage.Sets/src/mage/cards/b/BalduvianWarlord.java @@ -114,11 +114,7 @@ class BalduvianWarlordUnblockEffect extends OneShotEffect { filter.add(new PermanentReferenceInCollectionPredicate(list, game)); TargetPermanent target = new TargetPermanent(filter); target.withNotTarget(true); - if (target.canChoose(controller.getId(), source, game)) { - while (!target.isChosen(game) && target.canChoose(controller.getId(), source, game) && controller.canRespond()) { - controller.chooseTarget(outcome, target, source, game); - } - } else { + if (!controller.chooseTarget(outcome, target, source, game)) { return true; } Permanent chosenPermanent = game.getPermanent(target.getFirstTarget()); diff --git a/Mage.Sets/src/mage/cards/b/BanesInvoker.java b/Mage.Sets/src/mage/cards/b/BanesInvoker.java index 87585598d68..999377573ac 100644 --- a/Mage.Sets/src/mage/cards/b/BanesInvoker.java +++ b/Mage.Sets/src/mage/cards/b/BanesInvoker.java @@ -11,6 +11,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; +import mage.target.common.TargetCreaturePermanent; import java.util.UUID; @@ -30,6 +31,7 @@ public final class BanesInvoker extends CardImpl { // Wind Walk — {8}: Up to two target creatures each get +2/+2 and gain flying until end of turn. Ability ability = new SimpleActivatedAbility(new BoostTargetEffect(2, 2).setText("up to two target creatures each get +2/+2"), new GenericManaCost(8)); ability.addEffect(new GainAbilityTargetEffect(FlyingAbility.getInstance()).setText("and gain flying until end of turn")); + ability.addTarget(new TargetCreaturePermanent(0, 2)); this.addAbility(ability.withFlavorWord("Wind Walk")); } diff --git a/Mage.Sets/src/mage/cards/b/BarbaraWright.java b/Mage.Sets/src/mage/cards/b/BarbaraWright.java new file mode 100644 index 00000000000..be851f57e90 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BarbaraWright.java @@ -0,0 +1,43 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.common.SagaAbility; +import mage.abilities.keyword.DoctorsCompanionAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BarbaraWright extends CardImpl { + + public BarbaraWright(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ADVISOR); + this.power = new MageInt(1); + this.toughness = new MageInt(3); + + // History Teacher -- Sagas you control have read ahead. + this.addAbility(SagaAbility.makeGainReadAheadAbility().withFlavorWord("History Teacher")); + + // Doctor's companion + this.addAbility(DoctorsCompanionAbility.getInstance()); + } + + private BarbaraWright(final BarbaraWright card) { + super(card); + } + + @Override + public BarbaraWright copy() { + return new BarbaraWright(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BarbflareGremlin.java b/Mage.Sets/src/mage/cards/b/BarbflareGremlin.java new file mode 100644 index 00000000000..953825c35e2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BarbflareGremlin.java @@ -0,0 +1,88 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.TapForManaAllTriggeredManaAbility; +import mage.abilities.condition.common.SourceTappedCondition; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.mana.AddManaOfAnyTypeProducedEffect; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.abilities.keyword.HasteAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SetTargetPointer; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterLandPermanent; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BarbflareGremlin extends CardImpl { + + private static final FilterPermanent filter = new FilterLandPermanent("a player taps a land"); + + public BarbflareGremlin(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}"); + + this.subtype.add(SubType.GREMLIN); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // First strike + this.addAbility(FirstStrikeAbility.getInstance()); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // Whenever a player taps a land for mana, if Barbflare Gremlin is tapped, that player adds one mana of any type that land produced. Then that land deals 1 damage to that player. + Ability ability = new TapForManaAllTriggeredManaAbility( + new AddManaOfAnyTypeProducedEffect(), filter, SetTargetPointer.PERMANENT + ).withInterveningIf(SourceTappedCondition.TAPPED); + ability.addEffect(new BarbflareGremlinEffect()); + this.addAbility(ability); + } + + private BarbflareGremlin(final BarbflareGremlin card) { + super(card); + } + + @Override + public BarbflareGremlin copy() { + return new BarbflareGremlin(this); + } +} + +class BarbflareGremlinEffect extends OneShotEffect { + + BarbflareGremlinEffect() { + super(Outcome.Benefit); + staticText = "Then that land deals 1 damage to that player"; + } + + private BarbflareGremlinEffect(final BarbflareGremlinEffect effect) { + super(effect); + } + + @Override + public BarbflareGremlinEffect copy() { + return new BarbflareGremlinEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = (Permanent) getValue("tappedPermanent"); + if (permanent == null) { + return false; + } + Player player = game.getPlayer(permanent.getControllerId()); + return player != null && player.damage(1, permanent.getId(), source, game) > 0; + } +} diff --git a/Mage.Sets/src/mage/cards/b/BarretAvalancheLeader.java b/Mage.Sets/src/mage/cards/b/BarretAvalancheLeader.java new file mode 100644 index 00000000000..3759bcad629 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BarretAvalancheLeader.java @@ -0,0 +1,99 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.ReachAbility; +import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; +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.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.RebelRedToken; +import mage.target.TargetPermanent; +import mage.target.targetpointer.EachTargetPointer; + +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * @author TheElk801 + */ +public final class BarretAvalancheLeader extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledPermanent(SubType.EQUIPMENT); + private static final FilterPermanent filter2 = new FilterControlledPermanent(SubType.REBEL); + + public BarretAvalancheLeader(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.REBEL); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // Avalanche! -- Whenever an Equipment you control enters, create a 2/2 red Rebel creature token. + this.addAbility(new EntersBattlefieldAllTriggeredAbility( + new CreateTokenEffect(new RebelRedToken()), filter + ).withFlavorWord("Avalanche!")); + + // At the beginning of combat on your turn, attach up to one target Equipment you control to target Rebel you control. + Ability ability = new BeginningOfCombatTriggeredAbility(new BarretAvalancheLeaderEffect()); + ability.addTarget(new TargetPermanent(0, 1, filter)); + ability.addTarget(new TargetPermanent(filter2)); + this.addAbility(ability); + } + + private BarretAvalancheLeader(final BarretAvalancheLeader card) { + super(card); + } + + @Override + public BarretAvalancheLeader copy() { + return new BarretAvalancheLeader(this); + } +} + +class BarretAvalancheLeaderEffect extends OneShotEffect { + + BarretAvalancheLeaderEffect() { + super(Outcome.Benefit); + staticText = "attach up to one target Equipment you control to target Rebel you control"; + this.setTargetPointer(new EachTargetPointer()); + } + + private BarretAvalancheLeaderEffect(final BarretAvalancheLeaderEffect effect) { + super(effect); + } + + @Override + public BarretAvalancheLeaderEffect copy() { + return new BarretAvalancheLeaderEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + List permanents = this + .getTargetPointer() + .getTargets(game, source) + .stream() + .map(game::getPermanent) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + return permanents.size() >= 2 && permanents.get(1).addAttachment(permanents.get(0).getId(), source, game); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BarretWallace.java b/Mage.Sets/src/mage/cards/b/BarretWallace.java new file mode 100644 index 00000000000..6f5c711394a --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BarretWallace.java @@ -0,0 +1,65 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.abilities.keyword.ReachAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SetTargetPointer; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.permanent.EquippedPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BarretWallace extends CardImpl { + + private static final FilterPermanent filter + = new FilterControlledCreaturePermanent("equipped creatures you control"); + + static { + filter.add(EquippedPredicate.instance); + } + + private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter); + private static final Hint hint = new ValueHint("Equipped creatures you control", xValue); + + public BarretWallace(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.REBEL); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // Whenever Barret Wallace attacks, it deals damage equal to the number of equipped creatures you control to defending player. + this.addAbility(new AttacksTriggeredAbility( + new DamageTargetEffect(xValue, true, "it", true), + false, null, SetTargetPointer.PLAYER + ).withRuleTextReplacement(true).addHint(hint)); + } + + private BarretWallace(final BarretWallace card) { + super(card); + } + + @Override + public BarretWallace copy() { + return new BarretWallace(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BarrinsSpite.java b/Mage.Sets/src/mage/cards/b/BarrinsSpite.java index f90e68333c5..2763a578544 100644 --- a/Mage.Sets/src/mage/cards/b/BarrinsSpite.java +++ b/Mage.Sets/src/mage/cards/b/BarrinsSpite.java @@ -7,11 +7,12 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Zone; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.Target; -import mage.target.common.TargetCreaturePermanentSameController; +import mage.target.common.TargetPermanentSameController; import java.util.Collection; import java.util.List; @@ -29,7 +30,7 @@ public final class BarrinsSpite extends CardImpl { // Choose two target creatures controlled by the same player. Their controller chooses and sacrifices one of them. Return the other to its owner's hand. this.getSpellAbility().addEffect(new BarrinsSpiteEffect()); - this.getSpellAbility().addTarget(new TargetCreaturePermanentSameController(2)); + this.getSpellAbility().addTarget(new TargetPermanentSameController(StaticFilters.FILTER_PERMANENT_CREATURES)); } private BarrinsSpite(final BarrinsSpite card) { diff --git a/Mage.Sets/src/mage/cards/b/BartzAndBoko.java b/Mage.Sets/src/mage/cards/b/BartzAndBoko.java new file mode 100644 index 00000000000..968359caf92 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BartzAndBoko.java @@ -0,0 +1,93 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.AffinityEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetOpponentsCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BartzAndBoko extends CardImpl { + + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.BIRD, "Birds"); + private static final Hint hint = new ValueHint("Birds you control", new PermanentsOnBattlefieldCount(filter)); + + public BartzAndBoko(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.BIRD); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // Affinity for Birds + this.addAbility(new SimpleStaticAbility(Zone.ALL, new AffinityEffect(filter)).setRuleAtTheTop(true).addHint(hint)); + + // When Bartz and Boko enters, each other Bird you control deals damage equal to its power to target creature an opponent controls. + Ability ability = new EntersBattlefieldTriggeredAbility(new BartzAndBokoEffect()); + ability.addTarget(new TargetOpponentsCreaturePermanent()); + this.addAbility(ability); + } + + private BartzAndBoko(final BartzAndBoko card) { + super(card); + } + + @Override + public BartzAndBoko copy() { + return new BartzAndBoko(this); + } +} + +class BartzAndBokoEffect extends OneShotEffect { + + private static final FilterPermanent filter = new FilterControlledPermanent(SubType.BIRD); + + static { + filter.add(AnotherPredicate.instance); + } + + BartzAndBokoEffect() { + super(Outcome.Benefit); + staticText = "each other Bird you control deals damage equal to its power to target creature an opponent controls"; + } + + private BartzAndBokoEffect(final BartzAndBokoEffect effect) { + super(effect); + } + + @Override + public BartzAndBokoEffect copy() { + return new BartzAndBokoEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent == null) { + return false; + } + for (Permanent bird : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source, game)) { + permanent.damage(bird.getPower().getValue(), bird.getId(), source, game); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/b/BeatrixLoyalGeneral.java b/Mage.Sets/src/mage/cards/b/BeatrixLoyalGeneral.java new file mode 100644 index 00000000000..244ffd83d58 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BeatrixLoyalGeneral.java @@ -0,0 +1,89 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.VigilanceAbility; +import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; +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.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BeatrixLoyalGeneral extends CardImpl { + + public BeatrixLoyalGeneral(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{W}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // At the beginning of combat on your turn, you may attach any number of Equipment you control to target creature you control. + Ability ability = new BeginningOfCombatTriggeredAbility(new BeatrixLoyalGeneralEffect()); + ability.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(ability); + } + + private BeatrixLoyalGeneral(final BeatrixLoyalGeneral card) { + super(card); + } + + @Override + public BeatrixLoyalGeneral copy() { + return new BeatrixLoyalGeneral(this); + } +} + +class BeatrixLoyalGeneralEffect extends OneShotEffect { + + private static final FilterPermanent filter = new FilterControlledPermanent(SubType.EQUIPMENT); + + BeatrixLoyalGeneralEffect() { + super(Outcome.Benefit); + staticText = "you may attach any number of Equipment you control to target creature you control"; + } + + private BeatrixLoyalGeneralEffect(final BeatrixLoyalGeneralEffect effect) { + super(effect); + } + + @Override + public BeatrixLoyalGeneralEffect copy() { + return new BeatrixLoyalGeneralEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (player == null || permanent == null) { + return false; + } + TargetPermanent target = new TargetPermanent(0, Integer.MAX_VALUE, filter, true); + player.choose(outcome, target, source, game); + for (UUID targetId : target.getTargets()) { + permanent.addAttachment(targetId, source, game); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/b/BecomeTheAvalanche.java b/Mage.Sets/src/mage/cards/b/BecomeTheAvalanche.java index b8c2d218264..6131c6c2096 100644 --- a/Mage.Sets/src/mage/cards/b/BecomeTheAvalanche.java +++ b/Mage.Sets/src/mage/cards/b/BecomeTheAvalanche.java @@ -40,7 +40,7 @@ public final class BecomeTheAvalanche extends CardImpl { this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(xValue)); this.getSpellAbility().addEffect(new BoostControlledEffect( CardsInControllerHandCount.ANY, CardsInControllerHandCount.ANY, Duration.EndOfTurn - )); + ).setText("Then creatures you control get +X/+X until end of turn, where X is the number of cards in your hand")); this.getSpellAbility().addHint(hint); } diff --git a/Mage.Sets/src/mage/cards/b/BeltOfGiantStrength.java b/Mage.Sets/src/mage/cards/b/BeltOfGiantStrength.java index 40acc7e97f6..c4ed85111d1 100644 --- a/Mage.Sets/src/mage/cards/b/BeltOfGiantStrength.java +++ b/Mage.Sets/src/mage/cards/b/BeltOfGiantStrength.java @@ -4,10 +4,11 @@ import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.CostAdjuster; import mage.abilities.costs.mana.GenericManaCost; -import mage.abilities.effects.common.continuous.SetBasePowerToughnessEnchantedEffect; +import mage.abilities.effects.common.continuous.SetBasePowerToughnessAttachedEffect; import mage.abilities.keyword.EquipAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.constants.AttachmentType; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; @@ -30,8 +31,9 @@ public final class BeltOfGiantStrength extends CardImpl { this.subtype.add(SubType.EQUIPMENT); // Equipped creature has base power and toughness 10/10. - this.addAbility(new SimpleStaticAbility(new SetBasePowerToughnessEnchantedEffect(10, 10) - .setText("equipped creature has base power and toughness 10/10"))); + this.addAbility(new SimpleStaticAbility( + new SetBasePowerToughnessAttachedEffect(10, 10, AttachmentType.EQUIPMENT) + )); // Equip {10}. This ability costs {X} less to activate where X is the power of the creature it targets. EquipAbility ability = new EquipAbility(Outcome.BoostCreature, new GenericManaCost(10), new TargetControlledCreaturePermanent(), false); diff --git a/Mage.Sets/src/mage/cards/b/BetorKinToAll.java b/Mage.Sets/src/mage/cards/b/BetorKinToAll.java index 923f3db7a30..30b5a617f89 100644 --- a/Mage.Sets/src/mage/cards/b/BetorKinToAll.java +++ b/Mage.Sets/src/mage/cards/b/BetorKinToAll.java @@ -1,15 +1,15 @@ package mage.cards.b; -import java.util.UUID; - import mage.MageInt; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.condition.Condition; +import mage.abilities.decorator.ConditionalOneShotEffect; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.UntapAllEffect; import mage.abilities.hint.Hint; import mage.abilities.hint.ValueHint; import mage.abilities.keyword.FlyingAbility; @@ -21,22 +21,22 @@ import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.SuperType; import mage.filter.StaticFilters; -import mage.filter.common.FilterCreaturePermanent; import mage.game.Game; -import mage.game.permanent.Permanent; import mage.players.Player; +import java.util.UUID; + /** - * * @author androosss */ public final class BetorKinToAll extends CardImpl { private static final Hint hint = new ValueHint( - "Total toughness of creatures you control", ControlledCreaturesToughnessValue.instance); + "Total toughness of creatures you control", ControlledCreaturesToughnessValue.instance + ); public BetorKinToAll(UUID ownerId, CardSetInfo setInfo) { - super(ownerId, setInfo, new CardType[] { CardType.CREATURE }, "{2}{W}{B}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}{B}{G}"); this.supertype.add(SuperType.LEGENDARY); this.subtype.add(SubType.SPIRIT); @@ -47,15 +47,18 @@ public final class BetorKinToAll extends CardImpl { // Flying this.addAbility(FlyingAbility.getInstance()); - // At the beginning of your end step, if creatures you control have total - // toughness 10 or greater, draw a card. Then if creatures you control have - // total toughness 20 or greater, untap each creature you control. Then if - // creatures you control have total toughness 40 or greater, each opponent loses - // half their life, rounded up. - Ability betorAbility = new BeginningOfEndStepTriggeredAbility(new DrawCardSourceControllerEffect(1)) - .withInterveningIf(BetorKinToAllCondition.instance).addHint(hint); - betorAbility.addEffect(new BetorKinToAllEffect()); - this.addAbility(betorAbility); + // At the beginning of your end step, if creatures you control have total toughness 10 or greater, draw a card. Then if creatures you control have total toughness 20 or greater, untap each creature you control. Then if creatures you control have total toughness 40 or greater, each opponent loses half their life, rounded up. + Ability ability = new BeginningOfEndStepTriggeredAbility(new DrawCardSourceControllerEffect(1)) + .withInterveningIf(BetorKinToAllCondition.TEN); + ability.addEffect(new ConditionalOneShotEffect( + new UntapAllEffect(StaticFilters.FILTER_CONTROLLED_CREATURE), BetorKinToAllCondition.TWENTY, + "Then if creatures you control have total toughness 20 or greater, untap each creature you control" + )); + ability.addEffect(new ConditionalOneShotEffect( + new BetorKinToAllEffect(), BetorKinToAllCondition.FORTY, "Then if creatures you control " + + "have total toughness 40 or greater, each opponent loses half their life, rounded up" + )); + this.addAbility(ability.addHint(hint)); } private BetorKinToAll(final BetorKinToAll card) { @@ -69,22 +72,30 @@ public final class BetorKinToAll extends CardImpl { } enum BetorKinToAllCondition implements Condition { - instance; + TEN(10), + TWENTY(20), + FORTY(40); + private final int amount; + + private BetorKinToAllCondition(int amount) { + this.amount = amount; + } @Override public boolean apply(Game game, Ability source) { - return ControlledCreaturesToughnessValue.instance.calculate(game, source, null) >= 10; + return ControlledCreaturesToughnessValue.instance.calculate(game, source, null) >= amount; } + @Override + public String toString() { + return "creatures you control have total toughness " + amount + " or greater"; + } } class BetorKinToAllEffect extends OneShotEffect { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(); - BetorKinToAllEffect() { super(Outcome.Benefit); - this.staticText = "Then if creatures you control have total toughness 20 or greater, untap each creature you control. Then if creatures you control have total toughness 40 or greater, each opponent loses half their life, rounded up."; } private BetorKinToAllEffect(final BetorKinToAllEffect effect) { @@ -98,36 +109,12 @@ class BetorKinToAllEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller == null) { - return false; - } - - int sumToughness = ControlledCreaturesToughnessValue.instance.calculate(game, source, null); - - if (sumToughness < 20) { - return true; - } - for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, source.getControllerId(), - game)) { - permanent.untap(game); - } - - if (sumToughness < 40) { - return true; - } - - for (UUID playerId : game.getOpponents(controller.getId())) { + for (UUID playerId : game.getOpponents(source.getControllerId())) { Player opponent = game.getPlayer(playerId); - if (opponent == null) { - continue; - } - int amount = (int) Math.ceil(opponent.getLife() / 2f); - if (amount > 0) { - opponent.loseLife(amount, game, source, false); + if (opponent != null) { + opponent.loseLife(opponent.getLife() / 2 + opponent.getLife() % 2, game, source, false); } } - return true; } } @@ -162,4 +149,4 @@ enum ControlledCreaturesToughnessValue implements DynamicValue { public String toString() { return "X"; } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/b/BillPotts.java b/Mage.Sets/src/mage/cards/b/BillPotts.java index ad9402aa5a0..69fefc0f9ea 100644 --- a/Mage.Sets/src/mage/cards/b/BillPotts.java +++ b/Mage.Sets/src/mage/cards/b/BillPotts.java @@ -1,48 +1,38 @@ package mage.cards.b; import mage.MageInt; -import mage.MageItem; -import mage.abilities.Ability; import mage.abilities.Abilities; import mage.abilities.AbilitiesImpl; +import mage.abilities.Ability; import mage.abilities.Mode; import mage.abilities.common.ActivateAbilityTriggeredAbility; import mage.abilities.common.SpellCastControllerTriggeredAbility; import mage.abilities.effects.common.CopyStackObjectEffect; -import mage.abilities.meta.OrTriggeredAbility; -import mage.constants.SubType; -import mage.constants.SuperType; import mage.abilities.keyword.DoctorsCompanionAbility; +import mage.abilities.meta.OrTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.SetTargetPointer; -import mage.constants.Zone; -import mage.filter.common.FilterInstantOrSorcerySpell; +import mage.constants.*; import mage.filter.FilterSpell; import mage.filter.FilterStackObject; +import mage.filter.common.FilterInstantOrSorcerySpell; import mage.filter.predicate.ObjectSourcePlayer; import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.game.Game; -import mage.game.stack.StackObject; import mage.game.stack.Spell; +import mage.game.stack.StackObject; import mage.target.Target; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Objects; +import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; -import java.util.UUID; /** - * - * @author padfoot + * @author padfoot */ public final class BillPotts extends CardImpl { - private static final FilterSpell filterInstantOrSorcery = new FilterInstantOrSorcerySpell("an instant or sorcery that targets only {this}"); + private static final FilterSpell filterInstantOrSorcery = new FilterInstantOrSorcerySpell("an instant or sorcery spell that targets only {this}"); private static final FilterStackObject filterAbility = new FilterStackObject("an ability that targets only {this}"); static { @@ -52,32 +42,32 @@ public final class BillPotts extends CardImpl { public BillPotts(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}"); - + this.supertype.add(SuperType.LEGENDARY); this.subtype.add(SubType.HUMAN); this.power = new MageInt(2); this.toughness = new MageInt(4); // Whenever you cast an instant or sorcery spell that targets only Bill Potts or activate an ability that targets only Bill Potts, copy that spell or ability. You may choose new targets for the copy. This ability triggers only once each turn. - this.addAbility(new OrTriggeredAbility( - Zone.BATTLEFIELD, - new CopyStackObjectEffect("that spell or ability"), - false, - "", - new SpellCastControllerTriggeredAbility( - null, - filterInstantOrSorcery, - false, - SetTargetPointer.SPELL - ), - new ActivateAbilityTriggeredAbility( - null, - filterAbility, - SetTargetPointer.SPELL - ) + this.addAbility(new OrTriggeredAbility( + Zone.BATTLEFIELD, + new CopyStackObjectEffect("that spell or ability"), + false, + "", + new SpellCastControllerTriggeredAbility( + null, + filterInstantOrSorcery, + false, + SetTargetPointer.SPELL + ), + new ActivateAbilityTriggeredAbility( + null, + filterAbility, + SetTargetPointer.SPELL + ) ).setTriggersLimitEachTurn(1)); - // Doctor's companion + // Doctor's companion this.addAbility(DoctorsCompanionAbility.getInstance()); } @@ -97,7 +87,7 @@ enum BillPottsPredicate implements ObjectSourcePlayerPredicate { @Override public boolean apply(ObjectSourcePlayer input, Game game) { - List oneTargetList = Arrays.asList(input.getSourceId()); + List oneTargetList = Arrays.asList(input.getSourceId()); return (makeStream(input, game).collect(Collectors.toList()).equals(oneTargetList)); } @@ -108,9 +98,9 @@ enum BillPottsPredicate implements ObjectSourcePlayerPredicate { } else { objectAbilities.add(input.getObject().getStackAbility()); } - return objectAbilities - .stream() - .map(Ability::getModes) + return objectAbilities + .stream() + .map(Ability::getModes) .flatMap(m -> m.getSelectedModes().stream().map(m::get)) .filter(Objects::nonNull) .map(Mode::getTargets) diff --git a/Mage.Sets/src/mage/cards/b/Bioshift.java b/Mage.Sets/src/mage/cards/b/Bioshift.java index b7b43ecc093..b9946b01b63 100644 --- a/Mage.Sets/src/mage/cards/b/Bioshift.java +++ b/Mage.Sets/src/mage/cards/b/Bioshift.java @@ -87,7 +87,7 @@ class MoveCounterFromTargetToTargetEffect extends OneShotEffect { } int amountCounters = fromPermanent.getCounters(game).getCount(CounterType.P1P1); if (amountCounters > 0) { - int amountToMove = controller.getAmount(0, amountCounters, "Choose how many counters to move", game); + int amountToMove = controller.getAmount(0, amountCounters, "Choose how many counters to move", source, game); if (amountToMove > 0) { fromPermanent.removeCounters(CounterType.P1P1.createInstance(amountToMove), source, game); toPermanent.addCounters(CounterType.P1P1.createInstance(amountToMove), source.getControllerId(), source, game); diff --git a/Mage.Sets/src/mage/cards/b/BlackMagesRod.java b/Mage.Sets/src/mage/cards/b/BlackMagesRod.java new file mode 100644 index 00000000000..3cc617bcaf4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BlackMagesRod.java @@ -0,0 +1,62 @@ +package mage.cards.b; + +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.common.DamagePlayersEffect; +import mage.abilities.effects.common.continuous.AddCardSubtypeAttachedEffect; +import mage.abilities.effects.common.continuous.BoostEquippedEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.constants.AttachmentType; +import mage.constants.SubType; +import mage.abilities.keyword.EquipAbility; +import mage.abilities.keyword.JobSelectAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.TargetController; +import mage.filter.StaticFilters; + +/** + * @author balazskristof + */ +public final class BlackMagesRod extends CardImpl { + + public BlackMagesRod(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{B}"); + + this.subtype.add(SubType.EQUIPMENT); + + // Job select + this.addAbility(new JobSelectAbility()); + + // Equipped creature gets +1/+0, has "Whenever you cast a noncreature spell, this creature deals 1 damage to each opponent," and is a Wizard in addition to its other types. + Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(1, 0)); + ability.addEffect(new GainAbilityAttachedEffect( + new SpellCastControllerTriggeredAbility( + new DamagePlayersEffect(1, TargetController.OPPONENT, "this creature"), + StaticFilters.FILTER_SPELL_A_NON_CREATURE, false + ), + AttachmentType.EQUIPMENT + ).setText(", has \"Whenever you cast a noncreature spell, this creature deals 1 damage to each opponent,\"")); + ability.addEffect(new AddCardSubtypeAttachedEffect( + SubType.WIZARD, + AttachmentType.EQUIPMENT + ).setText("is a Wizard in addition to its other types").concatBy("and")); + this.addAbility(ability); + + // Equip {3} + this.addAbility(new EquipAbility(3)); + } + + private BlackMagesRod(final BlackMagesRod card) { + super(card); + } + + @Override + public BlackMagesRod copy() { + return new BlackMagesRod(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BlacksmithsTalent.java b/Mage.Sets/src/mage/cards/b/BlacksmithsTalent.java index 640f18a2487..5dbd9b4a5d1 100644 --- a/Mage.Sets/src/mage/cards/b/BlacksmithsTalent.java +++ b/Mage.Sets/src/mage/cards/b/BlacksmithsTalent.java @@ -1,7 +1,6 @@ package mage.cards.b; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.condition.common.MyTurnCondition; @@ -14,6 +13,7 @@ import mage.abilities.keyword.ClassLevelAbility; import mage.abilities.keyword.ClassReminderAbility; import mage.abilities.keyword.DoubleStrikeAbility; import mage.abilities.keyword.HasteAbility; +import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; @@ -74,7 +74,7 @@ public final class BlacksmithsTalent extends CardImpl { DoubleStrikeAbility.getInstance(), Duration.WhileOnBattlefield, filter2 ), MyTurnCondition.instance, "during your turn, equipped creatures you control have double strike")); ability.addEffect(new ConditionalContinuousEffect(new GainAbilityControlledEffect( - HasteAbility.getInstance(), Duration.WhileOnBattlefield + HasteAbility.getInstance(), Duration.WhileOnBattlefield, filter2 ), MyTurnCondition.instance, "and haste")); this.addAbility(new SimpleStaticAbility(new GainClassAbilitySourceEffect(ability, 3))); } diff --git a/Mage.Sets/src/mage/cards/b/BlightPile.java b/Mage.Sets/src/mage/cards/b/BlightPile.java index dc42b303d03..a9b7347d4ce 100644 --- a/Mage.Sets/src/mage/cards/b/BlightPile.java +++ b/Mage.Sets/src/mage/cards/b/BlightPile.java @@ -33,7 +33,7 @@ public final class BlightPile extends CardImpl { filter.add(new AbilityPredicate(DefenderAbility.class)); } - private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter); + private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter, null); private static final Hint hint = new ValueHint("Creatures with defender you control", xValue); public BlightPile(UUID ownerId, CardSetInfo setInfo) { @@ -47,7 +47,11 @@ public final class BlightPile extends CardImpl { this.addAbility(DefenderAbility.getInstance()); // {2}{B}, {T}: Each opponent loses X life, where X is the number of creatures with defender you control. - Ability ability = new SimpleActivatedAbility(new LoseLifeOpponentsEffect(xValue), new ManaCostsImpl<>("{2}{B}")); + Ability ability = new SimpleActivatedAbility( + new LoseLifeOpponentsEffect(xValue) + .setText("each opponent loses X life, where X is the number of creatures with defender you control"), + new ManaCostsImpl<>("{2}{B}") + ); ability.addCost(new TapSourceCost()); this.addAbility(ability.addHint(hint)); } diff --git a/Mage.Sets/src/mage/cards/b/Blink.java b/Mage.Sets/src/mage/cards/b/Blink.java new file mode 100644 index 00000000000..3189879ab0a --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/Blink.java @@ -0,0 +1,97 @@ +package mage.cards.b; + +import mage.abilities.Ability; +import mage.abilities.common.SagaAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.keyword.InvestigateEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SagaChapter; +import mage.constants.SubType; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.AlienAngelToken; +import mage.players.Player; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Blink extends CardImpl { + + public Blink(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{U}{B}"); + + this.subtype.add(SubType.SAGA); + + // (As this Saga enters and after your draw step, add a lore counter. Sacrifice after IV.) + SagaAbility sagaAbility = new SagaAbility(this, SagaChapter.CHAPTER_IV); + + // I, III -- Choose target creature. Its owner shuffles it into their library, then investigates. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_I, + new BlinkEffect(), new TargetCreaturePermanent() + ); + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_III, + new BlinkEffect(), new TargetCreaturePermanent() + ); + + // II, IV -- Create a 2/2 black Alien Angel artifact creature token with first strike, vigilance, and "Whenever an opponent casts a creature spell, this permanent isn't a creature until end of turn." + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_II, + new CreateTokenEffect(new AlienAngelToken()) + ); + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_IV, + new CreateTokenEffect(new AlienAngelToken()) + ); + this.addAbility(sagaAbility); + } + + private Blink(final Blink card) { + super(card); + } + + @Override + public Blink copy() { + return new Blink(this); + } +} + +class BlinkEffect extends OneShotEffect { + + BlinkEffect() { + super(Outcome.Benefit); + staticText = "choose target creature. Its owner shuffles it into their library, then investigates"; + } + + private BlinkEffect(final BlinkEffect effect) { + super(effect); + } + + @Override + public BlinkEffect copy() { + return new BlinkEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent == null) { + return false; + } + Player player = game.getPlayer(permanent.getOwnerId()); + if (player == null) { + return false; + } + player.shuffleCardsToLibrary(permanent, game, source); + InvestigateEffect.doInvestigate(player.getId(), 1, game, source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/b/BlueMagesCane.java b/Mage.Sets/src/mage/cards/b/BlueMagesCane.java new file mode 100644 index 00000000000..ebebb243889 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BlueMagesCane.java @@ -0,0 +1,108 @@ +package mage.cards.b; + +import java.util.UUID; + +import mage.ApprovingObject; +import mage.abilities.Ability; +import mage.abilities.SpellAbility; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.AddCardSubtypeAttachedEffect; +import mage.abilities.effects.common.continuous.BoostEquippedEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.keyword.EquipAbility; +import mage.cards.Card; +import mage.constants.*; +import mage.abilities.keyword.JobSelectAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.filter.common.FilterInstantOrSorceryCard; +import mage.filter.predicate.card.DefendingPlayerOwnsCardPredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCardInGraveyard; +import mage.util.CardUtil; + +/** + * @author balazskristof + */ +public final class BlueMagesCane extends CardImpl { + + private static final FilterInstantOrSorceryCard filter = new FilterInstantOrSorceryCard( + "instant or sorcery card from defending player's graveyard" + ); + + static { + filter.add(DefendingPlayerOwnsCardPredicate.instance); + } + + public BlueMagesCane(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{U}"); + + this.subtype.add(SubType.EQUIPMENT); + + // Job select + this.addAbility(new JobSelectAbility()); + + // Equipped creature gets +0/+2, is a Wizard in addition to its other types, and has "Whenever this creature attacks, exile up to one target instant or sorcery card from defending player's graveyard. If you do, copy it. You may cast the copy by paying {3} rather than paying its mana cost." + Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(0, 2)); + ability.addEffect(new AddCardSubtypeAttachedEffect(SubType.WIZARD, AttachmentType.EQUIPMENT) + .setText(", is a Wizard in addition to its other types") + ); + Ability attackAbility = new AttacksTriggeredAbility(new BlueMagesCaneEffect()); + attackAbility.addTarget(new TargetCardInGraveyard(0, 1, filter)); + ability.addEffect(new GainAbilityAttachedEffect(attackAbility, AttachmentType.EQUIPMENT) + .setText("and has \"Whenever this creature attacks, exile up to one target instant or sorcery card from defending player's graveyard. " + + "If you do, copy it. You may cast the copy by paying {3} rather than paying its mana cost.\"") + ); + this.addAbility(ability); + + // Spirit of the Whalaqee -- Equip {2} + this.addAbility(new EquipAbility(2).withFlavorWord("Spirit of the Whalaqee")); + } + + private BlueMagesCane(final BlueMagesCane card) { + super(card); + } + + @Override + public BlueMagesCane copy() { + return new BlueMagesCane(this); + } +} + +class BlueMagesCaneEffect extends OneShotEffect { + + BlueMagesCaneEffect() { + super(Outcome.Benefit); + staticText = "exile up to one target instant or sorcery card from defending player's graveyard. " + + "If you do, copy it. You may cast the copy by paying {3} rather than paying its mana cost."; + } + + private BlueMagesCaneEffect(final BlueMagesCaneEffect effect) { + super(effect); + } + + @Override + public BlueMagesCaneEffect copy() { + return new BlueMagesCaneEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); + if (controller == null || card == null) { + return false; + } + controller.moveCards(card, Zone.EXILED, source, game); + Card copiedCard = game.copyCard(card, source, source.getControllerId()); + if (!controller.chooseUse(Outcome.Benefit, "Cast " + copiedCard.getName() + " by paying {3}?", source, game)) { + return false; + } + CardUtil.castSingle(controller, source, game, copiedCard, new ManaCostsImpl<>("{3}")); + return true; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/b/BoneDevourer.java b/Mage.Sets/src/mage/cards/b/BoneDevourer.java index 79921a60ede..5b1908766b1 100644 --- a/Mage.Sets/src/mage/cards/b/BoneDevourer.java +++ b/Mage.Sets/src/mage/cards/b/BoneDevourer.java @@ -48,7 +48,7 @@ public final class BoneDevourer extends CardImpl { // When this creature dies, you draw X cards and you lose X life, where X is the number of +1/+1 counters on it. Ability ability = new DiesSourceTriggeredAbility(new DrawCardSourceControllerEffect(xValue).setText("you draw X cards")); - ability.addEffect(new LoseLifeSourceControllerEffect(xValue)); + ability.addEffect(new LoseLifeSourceControllerEffect(xValue).setText("and you lose X life, where X is the number of +1/+1 counters on it")); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/b/BoromirWardenOfTheTower.java b/Mage.Sets/src/mage/cards/b/BoromirWardenOfTheTower.java index f902918bb4d..d76f59ad088 100644 --- a/Mage.Sets/src/mage/cards/b/BoromirWardenOfTheTower.java +++ b/Mage.Sets/src/mage/cards/b/BoromirWardenOfTheTower.java @@ -3,7 +3,7 @@ package mage.cards.b; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; -import mage.abilities.common.SpellCastOpponentNoManaSpentTriggeredAbility; +import mage.abilities.common.SpellCastOpponentTriggeredAbility; import mage.abilities.costs.common.SacrificeSourceCost; import mage.abilities.effects.common.CounterTargetEffect; import mage.abilities.effects.common.continuous.GainAbilityAllEffect; @@ -12,23 +12,19 @@ import mage.abilities.keyword.IndestructibleAbility; import mage.abilities.keyword.VigilanceAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.SubType; -import mage.constants.SuperType; +import mage.constants.*; import mage.filter.StaticFilters; import java.util.UUID; /** - * * @author Susucr */ public final class BoromirWardenOfTheTower extends CardImpl { public BoromirWardenOfTheTower(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); - + this.supertype.add(SuperType.LEGENDARY); this.subtype.add(SubType.HUMAN); this.subtype.add(SubType.SOLDIER); @@ -39,12 +35,15 @@ public final class BoromirWardenOfTheTower extends CardImpl { this.addAbility(VigilanceAbility.getInstance()); // Whenever an opponent casts a spell, if no mana was spent to cast it, counter that spell. - this.addAbility(new SpellCastOpponentNoManaSpentTriggeredAbility(new CounterTargetEffect().setText("counter that spell"))); + this.addAbility(new SpellCastOpponentTriggeredAbility( + Zone.BATTLEFIELD, new CounterTargetEffect(), + StaticFilters.FILTER_SPELL_NO_MANA_SPENT, false, true + )); // Sacrifice Boromir, Warden of the Tower: Creatures you control gain indestructible until end of turn. The Ring tempts you. Ability ability = new SimpleActivatedAbility(new GainAbilityAllEffect( - IndestructibleAbility.getInstance(), Duration.EndOfTurn, - StaticFilters.FILTER_CONTROLLED_CREATURES, false + IndestructibleAbility.getInstance(), Duration.EndOfTurn, + StaticFilters.FILTER_CONTROLLED_CREATURES, false ), new SacrificeSourceCost()); ability.addEffect(new TheRingTemptsYouEffect()); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/b/BorosCharm.java b/Mage.Sets/src/mage/cards/b/BorosCharm.java index 4a714829ab5..585c00f2995 100644 --- a/Mage.Sets/src/mage/cards/b/BorosCharm.java +++ b/Mage.Sets/src/mage/cards/b/BorosCharm.java @@ -2,7 +2,6 @@ package mage.cards.b; import mage.abilities.Mode; import mage.abilities.effects.common.DamageTargetEffect; -import mage.abilities.effects.common.continuous.GainAbilityAllEffect; import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; import mage.abilities.keyword.DoubleStrikeAbility; @@ -11,6 +10,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; +import mage.filter.StaticFilters; import mage.target.common.TargetCreaturePermanent; import mage.target.common.TargetPlayerOrPlaneswalker; @@ -27,10 +27,12 @@ public final class BorosCharm extends CardImpl { //Choose one - Boros Charm deals 4 damage to target player this.getSpellAbility().addEffect(new DamageTargetEffect(4)); this.getSpellAbility().addTarget(new TargetPlayerOrPlaneswalker()); + //or permanents you control are indestructible this turn this.getSpellAbility().addMode(new Mode(new GainAbilityControlledEffect( - IndestructibleAbility.getInstance(), Duration.EndOfTurn + IndestructibleAbility.getInstance(), Duration.EndOfTurn, StaticFilters.FILTER_PERMANENTS ))); + //or target creature gains double strike until end of turn. Mode mode2 = new Mode(new GainAbilityTargetEffect(DoubleStrikeAbility.getInstance(), Duration.EndOfTurn)); mode2.addTarget(new TargetCreaturePermanent()); diff --git a/Mage.Sets/src/mage/cards/b/BraskasFinalAeon.java b/Mage.Sets/src/mage/cards/b/BraskasFinalAeon.java new file mode 100644 index 00000000000..13c1c757d9a --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BraskasFinalAeon.java @@ -0,0 +1,61 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.common.SagaAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.SacrificeOpponentsEffect; +import mage.abilities.effects.common.discard.DiscardEachPlayerEffect; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BraskasFinalAeon extends CardImpl { + + public BraskasFinalAeon(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, ""); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SAGA); + this.subtype.add(SubType.NIGHTMARE); + this.power = new MageInt(7); + this.toughness = new MageInt(7); + this.nightCard = true; + this.color.setBlack(true); + + // (As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.) + SagaAbility sagaAbility = new SagaAbility(this); + + // I, II -- Jecht Beam -- Each opponent discards a card and you draw a card. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_I, SagaChapter.CHAPTER_II, ability -> { + ability.addEffect(new DiscardEachPlayerEffect(TargetController.OPPONENT)); + ability.addEffect(new DrawCardSourceControllerEffect(1).concatBy("and you")); + ability.withFlavorWord("Jecht Beam"); + }); + + // III -- Ultimate Jecht Shot -- Each opponent sacrifices two creatures of their choice. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_III, ability -> { + ability.addEffect(new SacrificeOpponentsEffect(2, StaticFilters.FILTER_PERMANENT_CREATURES)); + ability.withFlavorWord("Ultimate Jecht Shot"); + }); + this.addAbility(sagaAbility); + + // Menace + this.addAbility(new MenaceAbility()); + } + + private BraskasFinalAeon(final BraskasFinalAeon card) { + super(card); + } + + @Override + public BraskasFinalAeon copy() { + return new BraskasFinalAeon(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BrenardGingerSculptor.java b/Mage.Sets/src/mage/cards/b/BrenardGingerSculptor.java index 5e8a142e5ac..5a5f0df714f 100644 --- a/Mage.Sets/src/mage/cards/b/BrenardGingerSculptor.java +++ b/Mage.Sets/src/mage/cards/b/BrenardGingerSculptor.java @@ -108,7 +108,7 @@ class BrenardGingerSculptorEffect extends OneShotEffect { effect.setBecomesArtifact(true); effect.withAdditionalSubType(SubType.FOOD); effect.withAdditionalSubType(SubType.GOLEM); - effect.addAdditionalAbilities(new FoodAbility(false)); + effect.addAdditionalAbilities(new FoodAbility()); player.moveCards(card, Zone.EXILED, source, game); effect.apply(game, source); diff --git a/Mage.Sets/src/mage/cards/b/BrineGiant.java b/Mage.Sets/src/mage/cards/b/BrineGiant.java index 98d9ca61f46..074145eb82a 100644 --- a/Mage.Sets/src/mage/cards/b/BrineGiant.java +++ b/Mage.Sets/src/mage/cards/b/BrineGiant.java @@ -2,15 +2,16 @@ package mage.cards.b; import mage.MageInt; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; -import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; +import mage.abilities.effects.common.AffinityEffect; +import mage.abilities.hint.Hint; import mage.abilities.hint.ValueHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; +import mage.filter.common.FilterControlledEnchantmentPermanent; import mage.filter.common.FilterControlledPermanent; import java.util.UUID; @@ -20,13 +21,8 @@ import java.util.UUID; */ public final class BrineGiant extends CardImpl { - static final FilterControlledPermanent filter = new FilterControlledPermanent("enchantment you control"); - - static { - filter.add(CardType.ENCHANTMENT.getPredicate()); - } - - private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter); + static final FilterControlledPermanent filter = new FilterControlledEnchantmentPermanent("enchantments"); + private static final Hint hint = new ValueHint("Enchantments you control", new PermanentsOnBattlefieldCount(filter)); public BrineGiant(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{6}{U}"); @@ -36,9 +32,7 @@ public final class BrineGiant extends CardImpl { this.toughness = new MageInt(6); // This spell costs {1} less to cast for each enchantment you control. - this.addAbility(new SimpleStaticAbility( - Zone.ALL, new SpellCostReductionForEachSourceEffect(1, xValue) - ).addHint(new ValueHint("Enchantments you control", xValue))); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new AffinityEffect(filter)).addHint(hint)); } private BrineGiant(final BrineGiant card) { @@ -49,4 +43,4 @@ public final class BrineGiant extends CardImpl { public BrineGiant copy() { return new BrineGiant(this); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/b/BroodcallerScourge.java b/Mage.Sets/src/mage/cards/b/BroodcallerScourge.java index 4a8145bbece..953fe67658f 100644 --- a/Mage.Sets/src/mage/cards/b/BroodcallerScourge.java +++ b/Mage.Sets/src/mage/cards/b/BroodcallerScourge.java @@ -69,8 +69,7 @@ enum BroodcallerScourgePredicate implements ObjectSourcePlayerPredicate { .getManaValue() <= CardUtil .getEffectValueFromAbility( - input.getSource(), "damage", - Integer.class, 0 - ); + input.getSource(), "damage", Integer.class + ).orElse(0); } } diff --git a/Mage.Sets/src/mage/cards/b/BrunaLightOfAlabaster.java b/Mage.Sets/src/mage/cards/b/BrunaLightOfAlabaster.java index de934d39ef3..bfb13f8b65d 100644 --- a/Mage.Sets/src/mage/cards/b/BrunaLightOfAlabaster.java +++ b/Mage.Sets/src/mage/cards/b/BrunaLightOfAlabaster.java @@ -135,17 +135,17 @@ class BrunaLightOfAlabasterEffect extends OneShotEffect { && controller.chooseUse(Outcome.Benefit, "Attach an Aura from your hand?", source, game)) { TargetCard targetAura = new TargetCard(Zone.HAND, filterAuraCard); if (!controller.choose(Outcome.Benefit, controller.getHand(), targetAura, source, game)) { - continue; + break; } Card aura = game.getCard(targetAura.getFirstTarget()); if (aura == null) { - continue; + break; } Target target = aura.getSpellAbility().getTargets().get(0); if (target == null) { - continue; + break; } fromHandGraveyard.add(aura); filterAuraCard.add(Predicates.not(new CardIdPredicate(aura.getId()))); @@ -159,17 +159,17 @@ class BrunaLightOfAlabasterEffect extends OneShotEffect { && controller.chooseUse(Outcome.Benefit, "Attach an Aura from your graveyard?", source, game)) { TargetCard targetAura = new TargetCard(Zone.GRAVEYARD, filterAuraCard); if (!controller.choose(Outcome.Benefit, controller.getGraveyard(), targetAura, source, game)) { - continue; + break; } Card aura = game.getCard(targetAura.getFirstTarget()); if (aura == null) { - continue; + break; } Target target = aura.getSpellAbility().getTargets().get(0); if (target == null) { - continue; + break; } fromHandGraveyard.add(aura); diff --git a/Mage.Sets/src/mage/cards/b/BugenhagenWiseElder.java b/Mage.Sets/src/mage/cards/b/BugenhagenWiseElder.java new file mode 100644 index 00000000000..160bde643b4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BugenhagenWiseElder.java @@ -0,0 +1,66 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.hint.ConditionHint; +import mage.abilities.hint.Hint; +import mage.abilities.keyword.ReachAbility; +import mage.abilities.mana.AnyColorManaAbility; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.mageobject.PowerPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BugenhagenWiseElder extends CardImpl { + + private static final FilterPermanent filter + = new FilterControlledCreaturePermanent("you control a creature with power 7 or greater"); + + static { + filter.add(new PowerPredicate(ComparisonType.MORE_THAN, 7)); + } + + private static final Condition condition = new PermanentsOnTheBattlefieldCondition(filter); + private static final Hint hint = new ConditionHint(condition, "You control a creature with power 7 or greater"); + + public BugenhagenWiseElder(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SHAMAN); + this.power = new MageInt(1); + this.toughness = new MageInt(3); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // At the beginning of your upkeep, if you control a creature with power 7 or greater, draw a card. + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new DrawCardSourceControllerEffect(1)).withInterveningIf(condition).addHint(hint)); + + // {T}: Add one mana of any color. + this.addAbility(new AnyColorManaAbility()); + } + + private BugenhagenWiseElder(final BugenhagenWiseElder card) { + super(card); + } + + @Override + public BugenhagenWiseElder copy() { + return new BugenhagenWiseElder(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BullRushBruiser.java b/Mage.Sets/src/mage/cards/b/BullRushBruiser.java index 9727549dd37..d69fb1ad63d 100644 --- a/Mage.Sets/src/mage/cards/b/BullRushBruiser.java +++ b/Mage.Sets/src/mage/cards/b/BullRushBruiser.java @@ -8,6 +8,7 @@ import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; import mage.abilities.keyword.FirstStrikeAbility; +import mage.constants.Duration; import mage.constants.SubType; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -37,7 +38,7 @@ public final class BullRushBruiser extends CardImpl { // Whenever Bull-Rush Bruiser attacks, if your team controls another Warrior, Bull-Rush Bruiser gains first strike until end of turn. this.addAbility(new ConditionalInterveningIfTriggeredAbility( - new AttacksTriggeredAbility(new GainAbilitySourceEffect(FirstStrikeAbility.getInstance()), false), + new AttacksTriggeredAbility(new GainAbilitySourceEffect(FirstStrikeAbility.getInstance(), Duration.EndOfTurn), false), new PermanentsOnTheBattlefieldCondition(filter), "Whenever {this} attacks, if your team controls another Warrior, " + "{this} gains first strike until end of turn." diff --git a/Mage.Sets/src/mage/cards/b/BulwarkOx.java b/Mage.Sets/src/mage/cards/b/BulwarkOx.java index dcb39c1a2a8..a438345f532 100644 --- a/Mage.Sets/src/mage/cards/b/BulwarkOx.java +++ b/Mage.Sets/src/mage/cards/b/BulwarkOx.java @@ -16,6 +16,9 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.CounterAnyPredicate; import mage.target.common.TargetCreaturePermanent; import java.util.UUID; @@ -25,6 +28,12 @@ import java.util.UUID; */ public final class BulwarkOx extends CardImpl { + private static final FilterPermanent filter = new FilterCreaturePermanent(); + + static { + filter.add(CounterAnyPredicate.instance); + } + public BulwarkOx(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); @@ -40,10 +49,10 @@ public final class BulwarkOx extends CardImpl { // Sacrifice this creature: Creatures you control with counters on them gain hexproof and indestructible until end of turn. ability = new SimpleActivatedAbility(new GainAbilityControlledEffect( - HexproofAbility.getInstance(), Duration.EndOfTurn + HexproofAbility.getInstance(), Duration.EndOfTurn, filter ).setText("creatures you control with counters on them gain hexproof"), new SacrificeSourceCost()); ability.addEffect(new GainAbilityControlledEffect( - IndestructibleAbility.getInstance(), Duration.EndOfTurn + IndestructibleAbility.getInstance(), Duration.EndOfTurn, filter ).setText("and indestructible until end of turn")); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/b/BurningOfXinye.java b/Mage.Sets/src/mage/cards/b/BurningOfXinye.java index a81caec204d..bfd7779a3a9 100644 --- a/Mage.Sets/src/mage/cards/b/BurningOfXinye.java +++ b/Mage.Sets/src/mage/cards/b/BurningOfXinye.java @@ -82,15 +82,12 @@ class BurningOfXinyeEffect extends OneShotEffect { Target target = new TargetControlledPermanent(amount, amount, filter, true); if (amount > 0 && target.canChoose(player.getId(), source, game)) { - while (!target.isChosen(game) && target.canChoose(player.getId(), source, game) && player.canRespond()) { - player.choose(Outcome.DestroyPermanent, target, source, game); - } - - for (UUID targetId : target.getTargets()) { - Permanent permanent = game.getPermanent(targetId); - - if (permanent != null) { - abilityApplied |= permanent.destroy(source, game, false); + if (player.choose(Outcome.DestroyPermanent, target, source, game)) { + for (UUID targetId : target.getTargets()) { + Permanent permanent = game.getPermanent(targetId); + if (permanent != null) { + abilityApplied |= permanent.destroy(source, game, false); + } } } } diff --git a/Mage.Sets/src/mage/cards/b/BusterSword.java b/Mage.Sets/src/mage/cards/b/BusterSword.java new file mode 100644 index 00000000000..e8b929dea56 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BusterSword.java @@ -0,0 +1,85 @@ +package mage.cards.b; + +import mage.abilities.Ability; +import mage.abilities.common.DealsCombatDamageEquippedTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DrawCardTargetEffect; +import mage.abilities.effects.common.continuous.BoostEquippedEffect; +import mage.abilities.keyword.EquipAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.predicate.mageobject.ManaValuePredicate; +import mage.game.Game; +import mage.players.Player; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BusterSword extends CardImpl { + + public BusterSword(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}"); + + this.subtype.add(SubType.EQUIPMENT); + + // Equipped creature gets +3/+2. + this.addAbility(new SimpleStaticAbility(new BoostEquippedEffect(3, 2))); + + // Whenever equipped creature deals combat damage to a player, draw a card, then you may cast a spell from your hand with mana value less than or equal to that damage without paying its mana cost. + Ability ability = new DealsCombatDamageEquippedTriggeredAbility(new DrawCardTargetEffect(1)); + ability.addEffect(new BusterSwordEffect()); + this.addAbility(ability); + + // Equip {2} + this.addAbility(new EquipAbility(2)); + } + + private BusterSword(final BusterSword card) { + super(card); + } + + @Override + public BusterSword copy() { + return new BusterSword(this); + } +} + +class BusterSwordEffect extends OneShotEffect { + + BusterSwordEffect() { + super(Outcome.Benefit); + staticText = ", then you may cast a spell from your hand with mana value " + + "less than or equal to that damage without paying its mana cost"; + } + + private BusterSwordEffect(final BusterSwordEffect effect) { + super(effect); + } + + @Override + public BusterSwordEffect copy() { + return new BusterSwordEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null || player.getHand().isEmpty()) { + return false; + } + int damage = (Integer) getValue("damage"); + FilterCard filter = new FilterCard(); + filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, damage + 1)); + return CardUtil.castSpellWithAttributesForFree(player, source, game, new CardsImpl(player.getHand()), filter); + } +} diff --git a/Mage.Sets/src/mage/cards/b/ByInvitationOnly.java b/Mage.Sets/src/mage/cards/b/ByInvitationOnly.java index 6cd8c608653..40c58ee16b0 100644 --- a/Mage.Sets/src/mage/cards/b/ByInvitationOnly.java +++ b/Mage.Sets/src/mage/cards/b/ByInvitationOnly.java @@ -58,7 +58,7 @@ class ByInvitationOnlyEffect extends OneShotEffect { return false; } int number = player.getAmount( - 0, 13, "Choose a number between 0 and 13", game + 0, 13, "Choose a number between 0 and 13", source, game ); return new SacrificeAllEffect( number, StaticFilters.FILTER_PERMANENT_CREATURE diff --git a/Mage.Sets/src/mage/cards/c/CaitSithFortuneTeller.java b/Mage.Sets/src/mage/cards/c/CaitSithFortuneTeller.java new file mode 100644 index 00000000000..93cf3644282 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CaitSithFortuneTeller.java @@ -0,0 +1,86 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CaitSithFortuneTeller extends CardImpl { + + public CaitSithFortuneTeller(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{3}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.CAT); + this.subtype.add(SubType.MOOGLE); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Lucky Slots -- At the beginning of combat on your turn, scry 1, then exile the top card of your library. You may play that card this turn. When you exile a card this way, target creature you control gets +X/+0 until end of turn, where X is that card's mana value. + this.addAbility(new BeginningOfCombatTriggeredAbility(new CaitSithFortuneTellerEffect()).withFlavorWord("Lucky Slots")); + } + + private CaitSithFortuneTeller(final CaitSithFortuneTeller card) { + super(card); + } + + @Override + public CaitSithFortuneTeller copy() { + return new CaitSithFortuneTeller(this); + } +} + +class CaitSithFortuneTellerEffect extends OneShotEffect { + + CaitSithFortuneTellerEffect() { + super(Outcome.Benefit); + staticText = "scry 1, then exile the top card of your library. You may play that card this turn. " + + "When you exile a card this way, target creature you control gets +X/+0 until end of turn, " + + "where X is that card's mana value"; + } + + private CaitSithFortuneTellerEffect(final CaitSithFortuneTellerEffect effect) { + super(effect); + } + + @Override + public CaitSithFortuneTellerEffect copy() { + return new CaitSithFortuneTellerEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + player.scry(1, source, game); + Card card = player.getLibrary().getFromTop(game); + if (card == null) { + return true; + } + player.moveCards(card, Zone.EXILED, source, game); + CardUtil.makeCardPlayable(game, source, card, false, Duration.EndOfTurn, false); + ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility( + new BoostTargetEffect(card.getManaValue(), 0), false + ); + ability.addTarget(new TargetControlledCreaturePermanent()); + game.fireReflexiveTriggeredAbility(ability, source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/c/CalixDestinysHand.java b/Mage.Sets/src/mage/cards/c/CalixDestinysHand.java index 1fa0cdcce47..0c1ef1215b3 100644 --- a/Mage.Sets/src/mage/cards/c/CalixDestinysHand.java +++ b/Mage.Sets/src/mage/cards/c/CalixDestinysHand.java @@ -13,6 +13,7 @@ import mage.cards.CardSetInfo; import mage.constants.*; import mage.filter.FilterCard; import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; import mage.filter.common.FilterControlledEnchantmentPermanent; import mage.filter.common.FilterEnchantmentCard; import mage.filter.predicate.Predicates; @@ -45,7 +46,6 @@ public final class CalixDestinysHand extends CardImpl { } private static final FilterPermanent filter3 = new FilterControlledEnchantmentPermanent(); - private static final FilterCard filter4 = new FilterEnchantmentCard("enchantment cards"); public CalixDestinysHand(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{2}{G}{W}"); @@ -70,7 +70,7 @@ public final class CalixDestinysHand extends CardImpl { this.addAbility(ability); // −7: Return all enchantment cards from your graveyard to the battlefield. - this.addAbility(new LoyaltyAbility(new ReturnFromYourGraveyardToBattlefieldAllEffect(filter4), -7)); + this.addAbility(new LoyaltyAbility(new ReturnFromYourGraveyardToBattlefieldAllEffect(StaticFilters.FILTER_CARD_ENCHANTMENTS), -7)); } private CalixDestinysHand(final CalixDestinysHand card) { @@ -109,8 +109,8 @@ class CalixDestinysHandExileEffect extends OneShotEffect { source.getTargets(); Permanent theirPerm = game.getPermanent(source.getTargets().get(0).getFirstTarget()); Permanent myPerm = game.getPermanent(source.getTargets().get(1).getFirstTarget()); - if (controller == null - || theirPerm == null + if (controller == null + || theirPerm == null || myPerm == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/c/CallForUnity.java b/Mage.Sets/src/mage/cards/c/CallForUnity.java index a6fdead9c67..c1c60ddeb73 100644 --- a/Mage.Sets/src/mage/cards/c/CallForUnity.java +++ b/Mage.Sets/src/mage/cards/c/CallForUnity.java @@ -1,46 +1,44 @@ - package mage.cards.c; -import java.util.UUID; -import mage.abilities.TriggeredAbility; -import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.condition.common.RevoltCondition; -import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.CountersSourceCount; -import mage.abilities.effects.Effect; import mage.abilities.effects.common.continuous.BoostControlledEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; 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.counters.CounterType; import mage.filter.StaticFilters; import mage.watchers.common.RevoltWatcher; +import java.util.UUID; + /** - * * @author fireshoes */ public final class CallForUnity extends CardImpl { - private static final String ruleText = "Revolt — At the beginning of your end step, if a permanent you controlled left the battlefield this turn, " - + "put a unity counter on {this}."; + private static final DynamicValue xValue = new CountersSourceCount(CounterType.UNITY); public CallForUnity(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{W}{W}"); // Revolt — At the beginning of your end step, if a permanent you controlled left the battlefield this turn, put a unity counter on Call for Unity. - TriggeredAbility ability = new BeginningOfEndStepTriggeredAbility(new AddCountersSourceEffect(CounterType.UNITY.createInstance(), true)); - this.addAbility(new ConditionalInterveningIfTriggeredAbility(ability, RevoltCondition.instance, ruleText).addHint(RevoltCondition.getHint()), new RevoltWatcher()); + this.addAbility(new BeginningOfEndStepTriggeredAbility(new AddCountersSourceEffect(CounterType.UNITY.createInstance())) + .withInterveningIf(RevoltCondition.instance) + .setAbilityWord(AbilityWord.REVOLT) + .addHint(RevoltCondition.getHint()), new RevoltWatcher()); // Creatures you control get +1/+1 for each unity counter on Call for Unity. - Effect effect = new BoostControlledEffect(new CountersSourceCount(CounterType.UNITY), new CountersSourceCount(CounterType.UNITY), Duration.WhileOnBattlefield, - StaticFilters.FILTER_PERMANENT_CREATURE, false); - effect.setText("Creatures you control get +1/+1 for each unity counter on {this}"); - this.addAbility(new SimpleStaticAbility(effect)); + this.addAbility(new SimpleStaticAbility(new BoostControlledEffect( + xValue, xValue, Duration.WhileOnBattlefield, + StaticFilters.FILTER_PERMANENT_CREATURE, false + ).setText("creatures you control get +1/+1 for each unity counter on {this}"))); } private CallForUnity(final CallForUnity card) { diff --git a/Mage.Sets/src/mage/cards/c/Camouflage.java b/Mage.Sets/src/mage/cards/c/Camouflage.java index 5444e3fcd1a..d192d4edc66 100644 --- a/Mage.Sets/src/mage/cards/c/Camouflage.java +++ b/Mage.Sets/src/mage/cards/c/Camouflage.java @@ -114,7 +114,7 @@ class CamouflageEffect extends ContinuousRuleModifyingEffectImpl { // (This temporarily manipulates Blocking values to "test" how many blockers the creature has still left to assign) List spentBlockers = new ArrayList<>(); for (Permanent possibleBlocker : list) { - if (possibleBlocker.getMaxBlocks() != 0 && possibleBlocker.getBlocking() >= possibleBlocker.getMaxBlocks()) { + if (possibleBlocker.getMaxBlocks() > 0 && possibleBlocker.getBlocking() >= possibleBlocker.getMaxBlocks()) { spentBlockers.add(possibleBlocker); } } diff --git a/Mage.Sets/src/mage/cards/c/Cannibalize.java b/Mage.Sets/src/mage/cards/c/Cannibalize.java index 9ddb072a474..13c4c4a69ad 100644 --- a/Mage.Sets/src/mage/cards/c/Cannibalize.java +++ b/Mage.Sets/src/mage/cards/c/Cannibalize.java @@ -8,10 +8,11 @@ import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Zone; import mage.counters.CounterType; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; -import mage.target.common.TargetCreaturePermanentSameController; +import mage.target.common.TargetPermanentSameController; import java.util.List; import java.util.Objects; @@ -28,7 +29,7 @@ public final class Cannibalize extends CardImpl { // Choose two target creatures controlled by the same player. Exile one of the creatures and put two +1/+1 counters on the other. this.getSpellAbility().addEffect(new CannibalizeEffect()); - this.getSpellAbility().addTarget(new TargetCreaturePermanentSameController(2)); + this.getSpellAbility().addTarget(new TargetPermanentSameController(StaticFilters.FILTER_PERMANENT_CREATURES)); } private Cannibalize(final Cannibalize card) { diff --git a/Mage.Sets/src/mage/cards/c/Cantivore.java b/Mage.Sets/src/mage/cards/c/Cantivore.java index c11797a82cc..2badf792783 100644 --- a/Mage.Sets/src/mage/cards/c/Cantivore.java +++ b/Mage.Sets/src/mage/cards/c/Cantivore.java @@ -1,45 +1,42 @@ - package mage.cards.c; -import java.util.UUID; import mage.MageInt; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.CardsInAllGraveyardsCount; import mage.abilities.effects.common.continuous.SetBasePowerToughnessSourceEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; import mage.abilities.keyword.VigilanceAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.FilterCard; +import mage.filter.StaticFilters; + +import java.util.UUID; /** - * * @author Loki (Mortivore), cbt33 */ public final class Cantivore extends CardImpl { - static final FilterCard filter = new FilterCard("enchantment cards"); - - static { - filter.add(CardType.ENCHANTMENT.getPredicate()); - } - + private static final DynamicValue xValue = new CardsInAllGraveyardsCount(StaticFilters.FILTER_CARD_ENCHANTMENTS); + private static final Hint hint = new ValueHint("Enchantment cards in all graveyards", xValue); + public Cantivore(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{W}{W}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}{W}"); this.subtype.add(SubType.LHURGOYF); this.power = new MageInt(0); this.toughness = new MageInt(0); - // Vigilance this.addAbility(VigilanceAbility.getInstance()); + // Cantivore's power and toughness are each equal to the number of enchantment cards in all graveyards. - DynamicValue value = (new CardsInAllGraveyardsCount(filter)); - this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(value))); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(xValue)).addHint(hint)); } private Cantivore(final Cantivore card) { diff --git a/Mage.Sets/src/mage/cards/c/CapitalCity.java b/Mage.Sets/src/mage/cards/c/CapitalCity.java new file mode 100644 index 00000000000..74e1ec58d77 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CapitalCity.java @@ -0,0 +1,47 @@ +package mage.cards.c; + +import mage.abilities.Ability; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.keyword.CyclingAbility; +import mage.abilities.mana.AnyColorManaAbility; +import mage.abilities.mana.ColorlessManaAbility; +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 CapitalCity extends CardImpl { + + public CapitalCity(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + this.subtype.add(SubType.TOWN); + + // {T}: Add {C}. + this.addAbility(new ColorlessManaAbility()); + + // {1}, {T}: Add one mana of any color. + Ability ability = new AnyColorManaAbility(new GenericManaCost(1)); + ability.addCost(new TapSourceCost()); + this.addAbility(ability); + + // Cycling {2} + this.addAbility(new CyclingAbility(new ManaCostsImpl<>("{2}"))); + } + + private CapitalCity(final CapitalCity card) { + super(card); + } + + @Override + public CapitalCity copy() { + return new CapitalCity(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CarrotCake.java b/Mage.Sets/src/mage/cards/c/CarrotCake.java index fea10dc1c2c..dbd1ba4c28e 100644 --- a/Mage.Sets/src/mage/cards/c/CarrotCake.java +++ b/Mage.Sets/src/mage/cards/c/CarrotCake.java @@ -37,7 +37,7 @@ public final class CarrotCake extends CardImpl { this.addAbility(ability); // {2}, {T}, Sacrifice Carrot Cake: You gain 3 life. - this.addAbility(new FoodAbility(true)); + this.addAbility(new FoodAbility()); } private CarrotCake(final CarrotCake card) { diff --git a/Mage.Sets/src/mage/cards/c/CecilDarkKnight.java b/Mage.Sets/src/mage/cards/c/CecilDarkKnight.java new file mode 100644 index 00000000000..f04cf84aa28 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CecilDarkKnight.java @@ -0,0 +1,85 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DealsDamageSourceTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.dynamicvalue.common.SavedDamageValue; +import mage.abilities.effects.common.LoseLifeSourceControllerEffect; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.effects.common.UntapSourceEffect; +import mage.abilities.keyword.DeathtouchAbility; +import mage.abilities.keyword.TransformAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.game.Controllable; +import mage.game.Game; +import mage.players.Player; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author balazskristof + */ +public final class CecilDarkKnight extends CardImpl { + + public CecilDarkKnight(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.KNIGHT); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + this.secondSideCardClazz = mage.cards.c.CecilRedeemedPaladin.class; + + // Deathtouch + this.addAbility(DeathtouchAbility.getInstance()); + + // Darkness -- Whenever Cecil deals damage, you lose that much life. Then if your life total is less than or equal to half your starting life total, untap Cecil and transform it. + this.addAbility(new TransformAbility()); + Ability ability = new DealsDamageSourceTriggeredAbility(new LoseLifeSourceControllerEffect(SavedDamageValue.MUCH), false).withFlavorWord("Darkness"); + ability.addEffect(new ConditionalOneShotEffect( + new UntapSourceEffect(), + CecilDarkKnightCondition.instance + ).addEffect( + new TransformSourceEffect(true).concatBy("and") + ).concatBy("Then")); + this.addAbility(ability); + } + + private CecilDarkKnight(final CecilDarkKnight card) { + super(card); + } + + @Override + public CecilDarkKnight copy() { + return new CecilDarkKnight(this); + } +} + +enum CecilDarkKnightCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + return Optional + .ofNullable(source) + .map(Controllable::getControllerId) + .map(game::getPlayer) + .map(Player::getLife) + .map(life -> life <= game.getStartingLife() / 2) + .orElse(false); + } + + @Override + public String toString() { + return "your life total is less than or equal to half your starting life total"; + } +} diff --git a/Mage.Sets/src/mage/cards/c/CecilRedeemedPaladin.java b/Mage.Sets/src/mage/cards/c/CecilRedeemedPaladin.java new file mode 100644 index 00000000000..2e6f4f4999c --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CecilRedeemedPaladin.java @@ -0,0 +1,52 @@ +package mage.cards.c; + +import java.util.UUID; +import mage.MageInt; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.StaticFilters; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.effects.common.continuous.GainAbilityAllEffect; +import mage.abilities.keyword.IndestructibleAbility; +import mage.abilities.keyword.LifelinkAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; + +/** + * @author balazskristof + */ +public final class CecilRedeemedPaladin extends CardImpl { + + public CecilRedeemedPaladin(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.KNIGHT); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + this.color.setWhite(true); + this.nightCard = true; + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // Protect -- Whenever Cecil attacks, other attacking creatures gain indestructible until end of turn. + this.addAbility(new AttacksTriggeredAbility(new GainAbilityAllEffect( + IndestructibleAbility.getInstance(), Duration.EndOfTurn, + StaticFilters.FILTER_ATTACKING_CREATURES, true + )).withFlavorWord("Protect")); + } + + private CecilRedeemedPaladin(final CecilRedeemedPaladin card) { + super(card); + } + + @Override + public CecilRedeemedPaladin copy() { + return new CecilRedeemedPaladin(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CemeteryDesecrator.java b/Mage.Sets/src/mage/cards/c/CemeteryDesecrator.java index 5ceb53e7acf..8e5a1ada5a7 100644 --- a/Mage.Sets/src/mage/cards/c/CemeteryDesecrator.java +++ b/Mage.Sets/src/mage/cards/c/CemeteryDesecrator.java @@ -166,7 +166,7 @@ class CemeteryDesecratorRemoveCountersEffect extends OneShotEffect { remainingCounters -= numCounters; int min = Math.max(0, countersLeftToRemove - remainingCounters); int max = Math.min(countersLeftToRemove, numCounters); - int toRemove = controller.getAmount(min, max, counterName + " counters to remove", game); + int toRemove = controller.getAmount(min, max, counterName + " counters to remove", source, game); // Sanity check in case of GUI bugs/disconnects toRemove = Math.max(toRemove, min); toRemove = Math.min(toRemove, max); diff --git a/Mage.Sets/src/mage/cards/c/ChampionsFromBeyond.java b/Mage.Sets/src/mage/cards/c/ChampionsFromBeyond.java new file mode 100644 index 00000000000..0653ffc4968 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/ChampionsFromBeyond.java @@ -0,0 +1,53 @@ +package mage.cards.c; + +import mage.abilities.Ability; +import mage.abilities.common.AttacksWithCreaturesTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.dynamicvalue.common.GetXValue; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.effects.keyword.ScryEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.game.permanent.token.HeroToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ChampionsFromBeyond extends CardImpl { + + public ChampionsFromBeyond(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{X}{W}{W}"); + + // When this enchantment enters, create X 1/1 colorless Hero creature tokens. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new HeroToken(), GetXValue.instance))); + + // Light Party -- Whenever you attack with four or more creatures, scry 2, then draw a card. + Ability ability = new AttacksWithCreaturesTriggeredAbility(new ScryEffect(2, false), 4); + ability.addEffect(new DrawCardSourceControllerEffect(1).concatBy(", then")); + this.addAbility(ability.withFlavorWord("Light Party")); + + // Full Party -- Whenever you attack with eight or more creatures, those creatures get +4/+4 until end of turn. + this.addAbility(new AttacksWithCreaturesTriggeredAbility( + Zone.BATTLEFIELD, + new BoostTargetEffect(4, 4) + .setText("those creatures get +4/+4 until end of turn"), + 8, StaticFilters.FILTER_PERMANENT_CREATURES, true + ).withFlavorWord("Full Party")); + } + + private ChampionsFromBeyond(final ChampionsFromBeyond card) { + super(card); + } + + @Override + public ChampionsFromBeyond copy() { + return new ChampionsFromBeyond(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/ChanceEncounter.java b/Mage.Sets/src/mage/cards/c/ChanceEncounter.java index 547ec691c43..856140a4c0f 100644 --- a/Mage.Sets/src/mage/cards/c/ChanceEncounter.java +++ b/Mage.Sets/src/mage/cards/c/ChanceEncounter.java @@ -1,21 +1,15 @@ - package mage.cards.c; -import mage.abilities.TriggeredAbility; -import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.common.WonCoinFlipControllerTriggeredAbility; +import mage.abilities.condition.Condition; import mage.abilities.condition.common.SourceHasCounterCondition; -import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.common.WinGameSourceControllerEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Zone; import mage.counters.CounterType; -import mage.game.Game; -import mage.game.events.CoinFlippedEvent; -import mage.game.events.GameEvent; import java.util.UUID; @@ -24,16 +18,18 @@ import java.util.UUID; */ public final class ChanceEncounter extends CardImpl { + private static final Condition condition = new SourceHasCounterCondition(CounterType.LUCK, 10); + public ChanceEncounter(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{R}{R}"); // Whenever you win a coin flip, put a luck counter on Chance Encounter. - this.addAbility(new ChanceEncounterTriggeredAbility()); + this.addAbility(new WonCoinFlipControllerTriggeredAbility( + new AddCountersSourceEffect(CounterType.LUCK.createInstance()) + )); // At the beginning of your upkeep, if Chance Encounter has ten or more luck counters on it, you win the game. - TriggeredAbility ability = new BeginningOfUpkeepTriggeredAbility(new WinGameSourceControllerEffect()); - this.addAbility(new ConditionalInterveningIfTriggeredAbility(ability, new SourceHasCounterCondition(CounterType.LUCK, 10, Integer.MAX_VALUE), - "At the beginning of your upkeep, if {this} has ten or more luck counters on it, you win the game.")); + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new WinGameSourceControllerEffect()).withInterveningIf(condition)); } private ChanceEncounter(final ChanceEncounter card) { @@ -45,33 +41,3 @@ public final class ChanceEncounter extends CardImpl { return new ChanceEncounter(this); } } - -class ChanceEncounterTriggeredAbility extends TriggeredAbilityImpl { - - ChanceEncounterTriggeredAbility() { - super(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.LUCK.createInstance()), false); - setTriggerPhrase("Whenever you win a coin flip, "); - } - - private ChanceEncounterTriggeredAbility(final ChanceEncounterTriggeredAbility ability) { - super(ability); - } - - @Override - public ChanceEncounterTriggeredAbility copy() { - return new ChanceEncounterTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.COIN_FLIPPED; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - CoinFlippedEvent flipEvent = (CoinFlippedEvent) event; - return flipEvent.getPlayerId().equals(controllerId) - && flipEvent.isWinnable() - && (flipEvent.getChosen() == flipEvent.getResult()); - } -} diff --git a/Mage.Sets/src/mage/cards/c/ChandraFlameshaper.java b/Mage.Sets/src/mage/cards/c/ChandraFlameshaper.java index 0f5222ca23d..999d9b23ab1 100644 --- a/Mage.Sets/src/mage/cards/c/ChandraFlameshaper.java +++ b/Mage.Sets/src/mage/cards/c/ChandraFlameshaper.java @@ -19,14 +19,13 @@ import mage.target.common.TargetCreatureOrPlaneswalkerAmount; import java.util.UUID; /** - * * @author ciaccona007 */ public final class ChandraFlameshaper extends CardImpl { public ChandraFlameshaper(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{5}{R}{R}"); - + this.supertype.add(SuperType.LEGENDARY); this.subtype.add(SubType.CHANDRA); this.setStartingLoyalty(6); @@ -37,13 +36,15 @@ public final class ChandraFlameshaper extends CardImpl { this.addAbility(plusTwoAbility); // +1: Create a token that's a copy of target creature you control, except it has haste and "At the beginning of the end step, sacrifice this token." - Ability[] extraAbilities = new Ability[2]; - extraAbilities[0] = HasteAbility.getInstance(); - extraAbilities[1] = new BeginningOfEndStepTriggeredAbility( - TargetController.NEXT, new SacrificeSourceEffect(), false - ); Ability plusOneAbility = new LoyaltyAbility( - new CreateTokenCopyTargetEffect().addAdditionalAbilities(extraAbilities), 1 + new CreateTokenCopyTargetEffect() + .addAdditionalAbilities( + HasteAbility.getInstance(), + new BeginningOfEndStepTriggeredAbility( + TargetController.NEXT, new SacrificeSourceEffect(), false + )).setText("create a token that's a copy of target creature you control, " + + "except it has haste and \"At the beginning of the end step, sacrifice this token.\""), + 1 ); plusOneAbility.addTarget(new TargetControlledCreaturePermanent()); this.addAbility(plusOneAbility); diff --git a/Mage.Sets/src/mage/cards/c/ChirrutImwe.java b/Mage.Sets/src/mage/cards/c/ChirrutImwe.java index 603907e8276..2e8be6534fe 100644 --- a/Mage.Sets/src/mage/cards/c/ChirrutImwe.java +++ b/Mage.Sets/src/mage/cards/c/ChirrutImwe.java @@ -1,6 +1,5 @@ package mage.cards.c; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; @@ -9,28 +8,22 @@ import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.Effect; import mage.abilities.effects.common.PreventCombatDamageToSourceEffect; -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.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 java.util.UUID; + /** - * * @author NinthWorld */ public final class ChirrutImwe extends CardImpl { public ChirrutImwe(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}{W}{U}"); - + this.supertype.add(SuperType.LEGENDARY); this.subtype.add(SubType.HUMAN); this.subtype.add(SubType.MONK); @@ -39,7 +32,7 @@ public final class ChirrutImwe extends CardImpl { // Chirrut Imwe can block up to two additional creatures. this.addAbility(new SimpleStaticAbility(new ChirrutImweEffect())); - + // {1}{W}: Prevent all combat damage that would be dealt to Chirrut Imwe until end of turn. Effect effect = new PreventCombatDamageToSourceEffect(Duration.EndOfTurn); effect.setText("Prevent all combat damage that would be dealt to {this} until end of turn"); @@ -57,29 +50,29 @@ public final class ChirrutImwe extends CardImpl { } class ChirrutImweEffect extends ContinuousEffectImpl { - + public ChirrutImweEffect() { super(Duration.WhileOnBattlefield, Outcome.Benefit); staticText = "{this} can block up to two additional creatures"; } - + private ChirrutImweEffect(final ChirrutImweEffect effect) { super(effect); } - + @Override public ChirrutImweEffect copy() { return new ChirrutImweEffect(this); } - + @Override public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { Permanent perm = game.getPermanent(source.getSourceId()); - if(perm != null) { - switch(layer) { + if (perm != null) { + switch (layer) { case RulesEffects: // maxBlocks = 0 equals to "can block any number of creatures" - if(perm.getMaxBlocks() > 0) { + if (perm.getMaxBlocks() > 0) { perm.setMaxBlocks(perm.getMaxBlocks() + 2); } break; diff --git a/Mage.Sets/src/mage/cards/c/ChocoSeekerOfParadise.java b/Mage.Sets/src/mage/cards/c/ChocoSeekerOfParadise.java new file mode 100644 index 00000000000..1f57195cc1d --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/ChocoSeekerOfParadise.java @@ -0,0 +1,105 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksWithCreaturesTriggeredAbility; +import mage.abilities.common.LandfallAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledPermanent; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInLibrary; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ChocoSeekerOfParadise extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledPermanent(SubType.BIRD, "Birds you control"); + + public ChocoSeekerOfParadise(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}{W}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.BIRD); + this.power = new MageInt(3); + this.toughness = new MageInt(5); + + // Whenever one or more Birds you control attack, look at that many cards from the top of your library. You may put one of them into your hand. Then put any number of land cards from among them onto the battlefield tapped and the rest into your graveyard. + this.addAbility(new AttacksWithCreaturesTriggeredAbility(new ChocoSeekerOfParadiseEffect(), 1, filter)); + + // Landfall -- Whenever a land you control enters, Choco gets +1/+0 until end of turn. + this.addAbility(new LandfallAbility(new BoostSourceEffect(1, 0, Duration.EndOfTurn))); + } + + private ChocoSeekerOfParadise(final ChocoSeekerOfParadise card) { + super(card); + } + + @Override + public ChocoSeekerOfParadise copy() { + return new ChocoSeekerOfParadise(this); + } +} + +class ChocoSeekerOfParadiseEffect extends OneShotEffect { + + ChocoSeekerOfParadiseEffect() { + super(Outcome.Benefit); + staticText = "look at that many cards from the top of your library. " + + "You may put one of them into your hand. Then put any number of land cards " + + "from among them onto the battlefield tapped and the rest into your graveyard"; + } + + private ChocoSeekerOfParadiseEffect(final ChocoSeekerOfParadiseEffect effect) { + super(effect); + } + + @Override + public ChocoSeekerOfParadiseEffect copy() { + return new ChocoSeekerOfParadiseEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + int amount = (Integer) getValue(AttacksWithCreaturesTriggeredAbility.VALUEKEY_NUMBER_ATTACKERS); + Cards cards = new CardsImpl(player.getLibrary().getTopCards(game, amount)); + cards.retainZone(Zone.LIBRARY, game); + if (cards.isEmpty()) { + return false; + } + TargetCard target = new TargetCardInLibrary(0, 1, StaticFilters.FILTER_CARD); + target.withChooseHint("to put into your hand"); + player.choose(outcome, cards, target, source, game); + Optional.ofNullable(target.getFirstTarget()) + .map(game::getCard) + .ifPresent(card -> player.moveCards(card, Zone.HAND, source, game)); + cards.retainZone(Zone.LIBRARY, game); + target = new TargetCardInLibrary(0, Integer.MAX_VALUE, StaticFilters.FILTER_CARD_LANDS); + target.withChooseHint("to put onto the battlefield tapped"); + player.choose(outcome, cards, target, source, game); + player.moveCards( + new CardsImpl(target.getTargets()).getCards(game), Zone.BATTLEFIELD, source, + game, true, false, false, null + ); + cards.retainZone(Zone.LIBRARY, game); + player.moveCards(cards, Zone.GRAVEYARD, source, game); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/c/ChocoboKnights.java b/Mage.Sets/src/mage/cards/c/ChocoboKnights.java new file mode 100644 index 00000000000..2f6ed39ee66 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/ChocoboKnights.java @@ -0,0 +1,52 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.common.AttacksWithCreaturesTriggeredAbility; +import mage.abilities.effects.common.continuous.GainAbilityAllEffect; +import mage.abilities.keyword.DoubleStrikeAbility; +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.FilterControlledCreaturePermanent; +import mage.filter.predicate.permanent.CounterAnyPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ChocoboKnights extends CardImpl { + + private static final FilterPermanent filter + = new FilterControlledCreaturePermanent("creatures you control with counters on them"); + + static { + filter.add(CounterAnyPredicate.instance); + } + + public ChocoboKnights(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.KNIGHT); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Whenever you attack, creatures you control with counters on them gain double strike until end of turn. + this.addAbility(new AttacksWithCreaturesTriggeredAbility(new GainAbilityAllEffect( + DoubleStrikeAbility.getInstance(), Duration.EndOfTurn, filter + ), 1)); + } + + private ChocoboKnights(final ChocoboKnights card) { + super(card); + } + + @Override + public ChocoboKnights copy() { + return new ChocoboKnights(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/ChoiceOfDamnations.java b/Mage.Sets/src/mage/cards/c/ChoiceOfDamnations.java index f313f08d145..df299d2eb0e 100644 --- a/Mage.Sets/src/mage/cards/c/ChoiceOfDamnations.java +++ b/Mage.Sets/src/mage/cards/c/ChoiceOfDamnations.java @@ -74,7 +74,7 @@ class ChoiceOfDamnationsEffect extends OneShotEffect { amount = Math.min(numberPermanents, safeLifeToLost); } else { // Human must choose - amount = targetPlayer.getAmount(0, Integer.MAX_VALUE, "Chooses a number", game); + amount = targetPlayer.getAmount(0, Integer.MAX_VALUE, "Chooses a number", source, game); } Player controller = game.getPlayer(source.getControllerId()); diff --git a/Mage.Sets/src/mage/cards/c/CidFreeflierPilot.java b/Mage.Sets/src/mage/cards/c/CidFreeflierPilot.java new file mode 100644 index 00000000000..5cbf6dc3c60 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CidFreeflierPilot.java @@ -0,0 +1,79 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.MyTurnCondition; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.effects.common.cost.SpellsCostReductionControllerEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicate; +import mage.filter.predicate.Predicates; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CidFreeflierPilot extends CardImpl { + + private static final FilterCard filter = new FilterCard("Equipment and Vehicle spells"); + private static final FilterCard filter2 = new FilterCard("Equipment or Vehicle card from your graveyard"); + private static final Predicate predicate = Predicates.or( + SubType.EQUIPMENT.getPredicate(), + SubType.VEHICLE.getPredicate() + ); + + static { + filter.add(predicate); + filter2.add(predicate); + } + + public CidFreeflierPilot(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.subtype.add(SubType.PILOT); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Equipment and Vehicle spells you cast cost {1} less to cast. + this.addAbility(new SimpleStaticAbility(new SpellsCostReductionControllerEffect(filter, 1))); + + // Jump -- During your turn, Cid has flying. + this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( + new GainAbilitySourceEffect(FlyingAbility.getInstance()), + MyTurnCondition.instance, "during your turn, {this} has flying" + )).withFlavorWord("Jump")); + + // {2}, {T}: Return target Equipment or Vehicle card from your graveyard to your hand. + Ability ability = new SimpleActivatedAbility(new ReturnFromGraveyardToHandTargetEffect(), new GenericManaCost(2)); + ability.addCost(new TapSourceCost()); + ability.addTarget(new TargetCardInYourGraveyard(filter2)); + this.addAbility(ability); + } + + private CidFreeflierPilot(final CidFreeflierPilot card) { + super(card); + } + + @Override + public CidFreeflierPilot copy() { + return new CidFreeflierPilot(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CidTimelessArtificer.java b/Mage.Sets/src/mage/cards/c/CidTimelessArtificer.java new file mode 100644 index 00000000000..eb4c97d973c --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CidTimelessArtificer.java @@ -0,0 +1,82 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.AdditiveDynamicValue; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.InfoEffect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.abilities.keyword.CyclingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterCard; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CidTimelessArtificer extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(); + private static final FilterCard filter2 = new FilterCard(); + + static { + filter.add(Predicates.or( + CardType.ARTIFACT.getPredicate(), + SubType.HERO.getPredicate() + )); + filter2.add(SubType.ARTIFICER.getPredicate()); + } + + private static final DynamicValue xValue = new AdditiveDynamicValue( + new PermanentsOnBattlefieldCount(new FilterControlledPermanent(SubType.ARTIFICER)), + new CardsInControllerGraveyardCount(filter2) + ); + private static final Hint hint = new ValueHint( + "Artificers you control and Artificer cards in your graveyard", xValue + ); + + public CidTimelessArtificer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ARTIFICER); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Artifact creatures and Heroes you control get +1/+1 for each Artificer you control and each Artificer card in your graveyard. + this.addAbility(new SimpleStaticAbility(new BoostControlledEffect( + xValue, xValue, Duration.WhileOnBattlefield, filter, false + ).setText("artifact creatures and Heroes you control get +1/+1 " + + "for each Artificer you control and each Artificer card in your graveyard")).addHint(hint)); + + // A deck can have any number of cards named Cid, Timeless Artificer. + this.getSpellAbility().addEffect(new InfoEffect("a deck can have any number of cards named Cid, Timeless Artificer")); + + // Cycling {W}{U} + this.addAbility(new CyclingAbility(new ManaCostsImpl<>("{W}{U}"))); + } + + private CidTimelessArtificer(final CidTimelessArtificer card) { + super(card); + } + + @Override + public CidTimelessArtificer copy() { + return new CidTimelessArtificer(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CircleOfPower.java b/Mage.Sets/src/mage/cards/c/CircleOfPower.java new file mode 100644 index 00000000000..3c8a732eb41 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CircleOfPower.java @@ -0,0 +1,53 @@ +package mage.cards.c; + +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.LoseLifeSourceControllerEffect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +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.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.game.permanent.token.BlackWizardToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CircleOfPower extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(SubType.WIZARD, ""); + private static final FilterPermanent filter2 = new FilterPermanent(SubType.WIZARD, ""); + + public CircleOfPower(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{B}"); + + // You draw two cards and you lose 2 life. Create a 0/1 black Wizard creature token with "Whenever you cast a noncreature spell, this token deals 1 damage to each opponent." + this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(2, true)); + this.getSpellAbility().addEffect(new LoseLifeSourceControllerEffect(2).concatBy("and")); + this.getSpellAbility().addEffect(new CreateTokenEffect(new BlackWizardToken())); + + // Wizards you control get +1/+0 and gain lifelink until end of turn. + this.getSpellAbility().addEffect(new BoostControlledEffect( + 1, 0, Duration.EndOfTurn + ).setText("Wizards you control get +1/+0").concatBy("
")); + this.getSpellAbility().addEffect(new GainAbilityControlledEffect( + LifelinkAbility.getInstance(), Duration.EndOfTurn, filter2 + ).setText("and gain lifelink until end of turn")); + } + + private CircleOfPower(final CircleOfPower card) { + super(card); + } + + @Override + public CircleOfPower copy() { + return new CircleOfPower(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CliveIfritsDominant.java b/Mage.Sets/src/mage/cards/c/CliveIfritsDominant.java new file mode 100644 index 00000000000..4555ed7dee1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CliveIfritsDominant.java @@ -0,0 +1,61 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.common.DevotionCount; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.ExileAndReturnSourceEffect; +import mage.abilities.effects.common.discard.DiscardHandControllerEffect; +import mage.abilities.keyword.TransformAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.PutCards; +import mage.constants.SubType; +import mage.constants.SuperType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CliveIfritsDominant extends CardImpl { + + public CliveIfritsDominant(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{R}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.NOBLE); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + this.secondSideCardClazz = mage.cards.i.IfritWardenOfInferno.class; + + // When Clive enters, you may discard your hand, then draw cards equal to your devotion to red. + Ability ability = new EntersBattlefieldTriggeredAbility(new DiscardHandControllerEffect(), true); + ability.addEffect(new DrawCardSourceControllerEffect(DevotionCount.R).concatBy(", then")); + this.addAbility(ability.addHint(DevotionCount.R.getHint())); + + // {4}{R}{R}, {T}: Exile Clive, then return it to the battlefield transformed under its owner's control. Activate only as a sorcery. + this.addAbility(new TransformAbility()); + ability = new ActivateAsSorceryActivatedAbility( + new ExileAndReturnSourceEffect(PutCards.BATTLEFIELD_TRANSFORMED), new ManaCostsImpl<>("{4}{R}{R}") + ); + ability.addCost(new TapSourceCost()); + this.addAbility(ability); + } + + private CliveIfritsDominant(final CliveIfritsDominant card) { + super(card); + } + + @Override + public CliveIfritsDominant copy() { + return new CliveIfritsDominant(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/ClockworkAvian.java b/Mage.Sets/src/mage/cards/c/ClockworkAvian.java index d532799878f..77f3d5787aa 100644 --- a/Mage.Sets/src/mage/cards/c/ClockworkAvian.java +++ b/Mage.Sets/src/mage/cards/c/ClockworkAvian.java @@ -104,7 +104,7 @@ class ClockworkAvianEffect extends OneShotEffect { return false; } int toAdd = player.getAmount( - 0, maxCounters, "Choose how many +1/+0 counters to put on " + permanent.getName(), game + 0, maxCounters, "Choose how many +1/+0 counters to put on " + permanent.getName(), source, game ); return toAdd > 0 && permanent.addCounters( CounterType.P1P0.createInstance(toAdd), source.getControllerId(), diff --git a/Mage.Sets/src/mage/cards/c/ClockworkBeast.java b/Mage.Sets/src/mage/cards/c/ClockworkBeast.java index c091cf4ad66..499f38d6457 100644 --- a/Mage.Sets/src/mage/cards/c/ClockworkBeast.java +++ b/Mage.Sets/src/mage/cards/c/ClockworkBeast.java @@ -101,7 +101,7 @@ class ClockworkBeastEffect extends OneShotEffect { return false; } int toAdd = player.getAmount( - 0, maxCounters, "Choose how many +1/+0 counters to put on " + permanent.getName(), game + 0, maxCounters, "Choose how many +1/+0 counters to put on " + permanent.getName(), source, game ); return toAdd > 0 && permanent.addCounters( CounterType.P1P0.createInstance(toAdd), source.getControllerId(), diff --git a/Mage.Sets/src/mage/cards/c/ClockworkSteed.java b/Mage.Sets/src/mage/cards/c/ClockworkSteed.java index e3c2c117c13..d658e619f68 100644 --- a/Mage.Sets/src/mage/cards/c/ClockworkSteed.java +++ b/Mage.Sets/src/mage/cards/c/ClockworkSteed.java @@ -109,7 +109,7 @@ class ClockworkSteedEffect extends OneShotEffect { return false; } int toAdd = player.getAmount( - 0, maxCounters, "Choose how many +1/+0 counters to put on " + permanent.getName(), game + 0, maxCounters, "Choose how many +1/+0 counters to put on " + permanent.getName(), source, game ); return toAdd > 0 && permanent.addCounters( CounterType.P1P0.createInstance(toAdd), source.getControllerId(), diff --git a/Mage.Sets/src/mage/cards/c/ClockworkSwarm.java b/Mage.Sets/src/mage/cards/c/ClockworkSwarm.java index 8902726f6fb..002b971d144 100644 --- a/Mage.Sets/src/mage/cards/c/ClockworkSwarm.java +++ b/Mage.Sets/src/mage/cards/c/ClockworkSwarm.java @@ -113,7 +113,7 @@ class ClockworkSwarmEffect extends OneShotEffect { return false; } int toAdd = player.getAmount( - 0, maxCounters, "Choose how many +1/+0 counters to put on " + permanent.getName(), game + 0, maxCounters, "Choose how many +1/+0 counters to put on " + permanent.getName(), source, game ); return toAdd > 0 && permanent.addCounters( CounterType.P1P0.createInstance(toAdd), source.getControllerId(), diff --git a/Mage.Sets/src/mage/cards/c/CloudMidgarMercenary.java b/Mage.Sets/src/mage/cards/c/CloudMidgarMercenary.java new file mode 100644 index 00000000000..27873a57983 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CloudMidgarMercenary.java @@ -0,0 +1,108 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.predicate.permanent.EquippedPredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.NumberOfTriggersEvent; +import mage.game.permanent.Permanent; +import mage.target.common.TargetCardInLibrary; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CloudMidgarMercenary extends CardImpl { + + private static final FilterCard filter = new FilterCard("an Equipment card"); + + static { + filter.add(SubType.EQUIPMENT.getPredicate()); + } + + public CloudMidgarMercenary(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.subtype.add(SubType.MERCENARY); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // When Cloud enters, search your library for an Equipment card, reveal it, put it into your hand, then shuffle. + this.addAbility(new EntersBattlefieldTriggeredAbility( + new SearchLibraryPutInHandEffect(new TargetCardInLibrary(filter), true) + )); + + // As long as Cloud is equipped, if an ability of Cloud or an Equipment attached to it triggers, that ability triggers an additional time. + this.addAbility(new SimpleStaticAbility(new CloudMidgarMercenaryEffect())); + } + + private CloudMidgarMercenary(final CloudMidgarMercenary card) { + super(card); + } + + @Override + public CloudMidgarMercenary copy() { + return new CloudMidgarMercenary(this); + } +} + +class CloudMidgarMercenaryEffect extends ReplacementEffectImpl { + + CloudMidgarMercenaryEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit); + staticText = "as long as {this} is equipped, if an ability of {this} " + + "or an Equipment attached to it triggers, that ability triggers an additional time"; + } + + private CloudMidgarMercenaryEffect(final CloudMidgarMercenaryEffect effect) { + super(effect); + } + + @Override + public CloudMidgarMercenaryEffect copy() { + return new CloudMidgarMercenaryEffect(this); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.NUMBER_OF_TRIGGERS; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + Permanent sourcePermanent = source.getSourcePermanentIfItStillExists(game); + if (sourcePermanent == null || !EquippedPredicate.instance.apply(sourcePermanent, game)) { + return false; + } + NumberOfTriggersEvent numberOfTriggersEvent = (NumberOfTriggersEvent) event; + GameEvent sourceEvent = numberOfTriggersEvent.getSourceEvent(); + if (sourceEvent == null) { + return false; + } + Permanent permanent = game.getPermanent(((NumberOfTriggersEvent) event).getSourceId()); + return permanent != null + && (permanent.equals(sourcePermanent) + || (permanent.hasSubtype(SubType.EQUIPMENT, game) + && sourcePermanent.getAttachments().contains(permanent.getId()))); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + event.setAmount(CardUtil.overflowInc(event.getAmount(), 1)); + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/c/CloudboundMoogle.java b/Mage.Sets/src/mage/cards/c/CloudboundMoogle.java new file mode 100644 index 00000000000..55f0dfd962c --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CloudboundMoogle.java @@ -0,0 +1,51 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.PlainscyclingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CloudboundMoogle extends CardImpl { + + public CloudboundMoogle(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}{W}"); + + this.subtype.add(SubType.MOOGLE); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When this creature enters, put a +1/+1 counter on target creature. + Ability ability = new EntersBattlefieldTriggeredAbility(new AddCountersTargetEffect(CounterType.P1P1.createInstance())); + ability.addTarget(new TargetCreaturePermanent()); + this.addAbility(ability); + + // Plainscycling {2} + this.addAbility(new PlainscyclingAbility(new ManaCostsImpl<>("{2}"))); + } + + private CloudboundMoogle(final CloudboundMoogle card) { + super(card); + } + + @Override + public CloudboundMoogle copy() { + return new CloudboundMoogle(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CloudsLimitBreak.java b/Mage.Sets/src/mage/cards/c/CloudsLimitBreak.java new file mode 100644 index 00000000000..25b96002dce --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CloudsLimitBreak.java @@ -0,0 +1,106 @@ +package mage.cards.c; + +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DestroyAllEffect; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.keyword.TieredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.TappedPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; + +import java.util.Objects; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CloudsLimitBreak extends CardImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent("tapped creature"); + + static { + filter.add(TappedPredicate.TAPPED); + } + + public CloudsLimitBreak(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{W}"); + + // Tiered + this.addAbility(new TieredAbility(this)); + + // * Cross-Slash -- {0} -- Destroy target tapped creature. + this.getSpellAbility().addEffect(new DestroyTargetEffect()); + this.getSpellAbility().addTarget(new TargetPermanent(filter)); + this.getSpellAbility().withFirstModeFlavorWord("Cross-Slash"); + this.getSpellAbility().withFirstModeCost(new GenericManaCost(0)); + + // * Blade Beam -- {1} -- Destroy any number of target tapped creatures with different controllers. + this.getSpellAbility().addMode(new Mode(new DestroyTargetEffect()) + .addTarget(new CloudsLimitBreakTarget()) + .withFlavorWord("Blade Beam") + .withCost(new GenericManaCost(1))); + + // * Omnislash -- {3}{W} -- Destroy all tapped creatures. + this.getSpellAbility().addMode(new Mode(new DestroyAllEffect(filter) + .setText("destroy all tapped creatures")) + .withFlavorWord("Omnislash") + .withCost(new ManaCostsImpl<>("{3}{W}"))); + } + + private CloudsLimitBreak(final CloudsLimitBreak card) { + super(card); + } + + @Override + public CloudsLimitBreak copy() { + return new CloudsLimitBreak(this); + } +} + +class CloudsLimitBreakTarget extends TargetCreaturePermanent { + + private static final FilterCreaturePermanent filter + = new FilterCreaturePermanent("tapped creatures with different controllers"); + + CloudsLimitBreakTarget() { + super(0, Integer.MAX_VALUE, filter, false); + } + + private CloudsLimitBreakTarget(final CloudsLimitBreakTarget target) { + super(target); + } + + @Override + public CloudsLimitBreakTarget copy() { + return new CloudsLimitBreakTarget(this); + } + + @Override + public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { + if (!super.canTarget(controllerId, id, source, game)) { + return false; + } + Permanent creature = game.getPermanent(id); + if (creature == null) { + return false; + } + return this + .getTargets() + .stream() + .map(game::getPermanent) + .filter(Objects::nonNull) + .noneMatch(permanent -> !creature.getId().equals(permanent.getId()) + && creature.isControlledBy(permanent.getControllerId()) + ); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CoercedToKill.java b/Mage.Sets/src/mage/cards/c/CoercedToKill.java index 5cc229736b6..6f5011a428e 100644 --- a/Mage.Sets/src/mage/cards/c/CoercedToKill.java +++ b/Mage.Sets/src/mage/cards/c/CoercedToKill.java @@ -7,7 +7,7 @@ import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.continuous.AddCardSubtypeAttachedEffect; import mage.abilities.effects.common.continuous.ControlEnchantedEffect; import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; -import mage.abilities.effects.common.continuous.SetBasePowerToughnessEnchantedEffect; +import mage.abilities.effects.common.continuous.SetBasePowerToughnessAttachedEffect; import mage.abilities.keyword.DeathtouchAbility; import mage.constants.*; import mage.target.common.TargetCreaturePermanent; @@ -37,7 +37,7 @@ public final class CoercedToKill extends CardImpl { this.addAbility(new SimpleStaticAbility(new ControlEnchantedEffect())); // Enchanted creature has base power and toughness 1/1, has deathtouch, and is an Assassin in addition to its other types. - Ability ability = new SimpleStaticAbility(new SetBasePowerToughnessEnchantedEffect(1, 1)); + Ability ability = new SimpleStaticAbility(new SetBasePowerToughnessAttachedEffect(1, 1, AttachmentType.AURA)); ability.addEffect(new GainAbilityAttachedEffect(DeathtouchAbility.getInstance(), AttachmentType.AURA) .setText(", has deathtouch")); ability.addEffect(new AddCardSubtypeAttachedEffect(SubType.ASSASSIN, AttachmentType.AURA) diff --git a/Mage.Sets/src/mage/cards/c/Coeurl.java b/Mage.Sets/src/mage/cards/c/Coeurl.java new file mode 100644 index 00000000000..2c678664c4f --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/Coeurl.java @@ -0,0 +1,54 @@ +package mage.cards.c; + +import mage.MageInt; +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.TapTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Coeurl extends CardImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent("nonenchantment creature"); + + static { + filter.add(Predicates.not(CardType.ENCHANTMENT.getPredicate())); + } + + public Coeurl(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.subtype.add(SubType.CAT); + this.subtype.add(SubType.BEAST); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // {1}{W}, {T}: Tap target nonenchantment creature. + Ability ability = new SimpleActivatedAbility(new TapTargetEffect(), new ManaCostsImpl<>("{1}{W}")); + ability.addCost(new TapSourceCost()); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + } + + private Coeurl(final Coeurl card) { + super(card); + } + + @Override + public Coeurl copy() { + return new Coeurl(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/ColossalGraveReaver.java b/Mage.Sets/src/mage/cards/c/ColossalGraveReaver.java index 3b130b45c27..25eb35f6cf4 100644 --- a/Mage.Sets/src/mage/cards/c/ColossalGraveReaver.java +++ b/Mage.Sets/src/mage/cards/c/ColossalGraveReaver.java @@ -1,11 +1,5 @@ package mage.cards.c; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; - import mage.MageInt; import mage.abilities.Ability; import mage.abilities.BatchTriggeredAbility; @@ -13,11 +7,11 @@ import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.MillCardsControllerEffect; +import mage.abilities.keyword.FlyingAbility; import mage.cards.*; +import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; -import mage.abilities.keyword.FlyingAbility; -import mage.constants.CardType; import mage.constants.Zone; import mage.filter.FilterCard; import mage.game.Game; @@ -28,15 +22,20 @@ import mage.players.Player; import mage.target.TargetCard; import mage.target.targetpointer.FixedTargets; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + /** - * * @author Grath */ public final class ColossalGraveReaver extends CardImpl { public ColossalGraveReaver(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{6}{B}{G}"); - + this.subtype.add(SubType.DRAGON); this.power = new MageInt(7); this.toughness = new MageInt(6); @@ -159,6 +158,6 @@ class ColossalGraveReaverTriggeredAbility extends TriggeredAbilityImpl implement @Override public String getRule() { return "Whenever one or more creature cards are put into your graveyard " + - "from your library, put one of them onto the battlefield tapped."; + "from your library, put one of them onto the battlefield."; } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/c/CommuneWithBeavers.java b/Mage.Sets/src/mage/cards/c/CommuneWithBeavers.java new file mode 100644 index 00000000000..4531503444e --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CommuneWithBeavers.java @@ -0,0 +1,44 @@ +package mage.cards.c; + +import java.util.UUID; + +import mage.abilities.effects.common.LookLibraryAndPickControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.PutCards; +import mage.filter.FilterCard; +import mage.filter.common.FilterPermanentCard; +import mage.filter.predicate.Predicates; + +/** + * @author balazskristof + */ +public final class CommuneWithBeavers extends CardImpl { + + private static final FilterCard filter = new FilterPermanentCard("an artifact, creature, or land card"); + + static { + filter.add(Predicates.or( + CardType.ARTIFACT.getPredicate(), + CardType.CREATURE.getPredicate(), + CardType.LAND.getPredicate() + )); + } + + public CommuneWithBeavers(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{G}"); + + // Look at the top three cards of your library. You may reveal an artifact, creature, or land card from among them and put it into your hand. Put the rest on hte bottom of your library in any order. + this.getSpellAbility().addEffect(new LookLibraryAndPickControllerEffect(3, 1, filter, PutCards.HAND, PutCards.BOTTOM_ANY)); + } + + private CommuneWithBeavers(final CommuneWithBeavers card) { + super(card); + } + + @Override + public CommuneWithBeavers copy() { + return new CommuneWithBeavers(this); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/c/ContractualSafeguard.java b/Mage.Sets/src/mage/cards/c/ContractualSafeguard.java index b22289e1627..f4f88fece39 100644 --- a/Mage.Sets/src/mage/cards/c/ContractualSafeguard.java +++ b/Mage.Sets/src/mage/cards/c/ContractualSafeguard.java @@ -151,7 +151,8 @@ class ContractualSafeguardSecondEffect extends OneShotEffect { chosenType = choice.getChoice(); } CounterType counterType = CounterType.findByName(chosenType); - for (Permanent creature : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source, game)) { + for (Permanent creature : game.getBattlefield().getActivePermanents( + StaticFilters.FILTER_CONTROLLED_CREATURES, source.getControllerId(), source, game)) { if (!creature.getId().equals(permanent.getId())) { creature.addCounters(counterType.createInstance(), source, game); } diff --git a/Mage.Sets/src/mage/cards/c/CountlessGearsRenegade.java b/Mage.Sets/src/mage/cards/c/CountlessGearsRenegade.java index 1e25f1633ec..6b04aa94436 100644 --- a/Mage.Sets/src/mage/cards/c/CountlessGearsRenegade.java +++ b/Mage.Sets/src/mage/cards/c/CountlessGearsRenegade.java @@ -1,10 +1,8 @@ package mage.cards.c; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.condition.common.RevoltCondition; -import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.common.CreateTokenEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -31,13 +29,10 @@ public final class CountlessGearsRenegade extends CardImpl { // Revolt — When Countless Gears Renegade enters the battlefield, if a permanent you controlled // left the battlefield this turn, create a 1/1 colorless Servo artifact creature token. - Ability ability = new ConditionalInterveningIfTriggeredAbility( - new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new ServoToken()), false), - RevoltCondition.instance, "When {this} enters, if a permanent you controlled " + - "left the battlefield this turn, create a 1/1 colorless Servo artifact creature token." - ); - ability.setAbilityWord(AbilityWord.REVOLT); - this.addAbility(ability.addHint(RevoltCondition.getHint()), new RevoltWatcher()); + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new ServoToken())) + .withInterveningIf(RevoltCondition.instance) + .setAbilityWord(AbilityWord.REVOLT) + .addHint(RevoltCondition.getHint()), new RevoltWatcher()); } private CountlessGearsRenegade(final CountlessGearsRenegade card) { diff --git a/Mage.Sets/src/mage/cards/c/CruelTruths.java b/Mage.Sets/src/mage/cards/c/CruelTruths.java index d2434e39a1f..5bed94d32b6 100644 --- a/Mage.Sets/src/mage/cards/c/CruelTruths.java +++ b/Mage.Sets/src/mage/cards/c/CruelTruths.java @@ -18,7 +18,7 @@ public final class CruelTruths extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{B}"); // Surveil 2, then draw two cards. You lose 2 life. - this.getSpellAbility().addEffect(new SurveilEffect(2)); + this.getSpellAbility().addEffect(new SurveilEffect(2, false)); this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(2).concatBy(", then")); this.getSpellAbility().addEffect(new LoseLifeSourceControllerEffect(2)); } diff --git a/Mage.Sets/src/mage/cards/c/CrystallizedSerah.java b/Mage.Sets/src/mage/cards/c/CrystallizedSerah.java new file mode 100644 index 00000000000..aa2e88a4097 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CrystallizedSerah.java @@ -0,0 +1,45 @@ +package mage.cards.c; + +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.s.SerahFarron; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SuperType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CrystallizedSerah extends CardImpl { + + public CrystallizedSerah(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, ""); + + this.supertype.add(SuperType.LEGENDARY); + this.nightCard = true; + this.color.setGreen(true); + this.color.setWhite(true); + + // The first legendary creature spell you cast each turn costs {2} less to cast. + this.addAbility(SerahFarron.makeAbility()); + + // Legendary creatures you control get +2/+2. + this.addAbility(new SimpleStaticAbility(new BoostControlledEffect( + 2, 2, Duration.WhileControlled, StaticFilters.FILTER_CREATURES_LEGENDARY + ))); + } + + private CrystallizedSerah(final CrystallizedSerah card) { + super(card); + } + + @Override + public CrystallizedSerah copy() { + return new CrystallizedSerah(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CullingRitual.java b/Mage.Sets/src/mage/cards/c/CullingRitual.java index d6bf290d208..1ba63fdf0da 100644 --- a/Mage.Sets/src/mage/cards/c/CullingRitual.java +++ b/Mage.Sets/src/mage/cards/c/CullingRitual.java @@ -80,7 +80,7 @@ class CullingRitualEffect extends OneShotEffect { } int black = player.getAmount( 0, counter, counter + " permanents were destroyed, " + - "choose the amount of black mana to produce (the rest will be green)", game + "choose the amount of black mana to produce (the rest will be green)", source, game ); Mana mana = new Mana(ManaType.BLACK, black); if (black < counter) { diff --git a/Mage.Sets/src/mage/cards/c/CyanVengefulSamurai.java b/Mage.Sets/src/mage/cards/c/CyanVengefulSamurai.java new file mode 100644 index 00000000000..37bc5d6a351 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CyanVengefulSamurai.java @@ -0,0 +1,63 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.common.CardsLeaveGraveyardTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; +import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +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.constants.Zone; +import mage.counters.CounterType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CyanVengefulSamurai extends CardImpl { + + private static final DynamicValue xValue = new CardsInControllerGraveyardCount(StaticFilters.FILTER_CARD_CREATURE, 1); + private static final Hint hint = new ValueHint("Creatures in your graveyard", xValue); + + public CyanVengefulSamurai(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{6}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SAMURAI); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // This spell costs {1} less to cast for each creature card in your graveyard. + this.addAbility(new SimpleStaticAbility( + Zone.ALL, new SpellCostReductionForEachSourceEffect(1, xValue) + ).addHint(hint)); + + // Double strike + this.addAbility(DoubleStrikeAbility.getInstance()); + + // Whenever one or more creature cards leave your graveyard, put a +1/+1 counter on Cyan. + this.addAbility(new CardsLeaveGraveyardTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), StaticFilters.FILTER_CARD_CREATURES + )); + } + + private CyanVengefulSamurai(final CyanVengefulSamurai card) { + super(card); + } + + @Override + public CyanVengefulSamurai copy() { + return new CyanVengefulSamurai(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DancersChakrams.java b/Mage.Sets/src/mage/cards/d/DancersChakrams.java new file mode 100644 index 00000000000..186fe56e841 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DancersChakrams.java @@ -0,0 +1,69 @@ +package mage.cards.d; + +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.*; +import mage.abilities.keyword.LifelinkAbility; +import mage.cards.l.Lifelink; +import mage.constants.AttachmentType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.abilities.keyword.EquipAbility; +import mage.abilities.keyword.JobSelectAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.mageobject.CommanderPredicate; + +/** + * @author balazskristof + */ +public final class DancersChakrams extends CardImpl { + + private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("commanders you control"); + private static final FilterControlledPermanent filter2 = new FilterControlledPermanent("commanders you control"); + + static { + filter.add(CommanderPredicate.instance); + filter2.add(CommanderPredicate.instance); + } + + public DancersChakrams(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}{W}"); + + this.subtype.add(SubType.EQUIPMENT); + + // Job select + this.addAbility(new JobSelectAbility()); + + // Equipped creature gets +2/+2, has lifelink and "Other commanders you control get +2/+2 and have lifelink," and is a Performer in addition to its other types. + Ability commanderAbility = new SimpleStaticAbility(new BoostAllEffect(2, 2, Duration.WhileOnBattlefield, filter, true)); + commanderAbility.addEffect(new GainAbilityControlledEffect(LifelinkAbility.getInstance(), Duration.WhileOnBattlefield, filter2) + .setText("and have lifelink")); + Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(2, 2)); + ability.addEffect(new GainAbilityAttachedEffect(LifelinkAbility.getInstance(), AttachmentType.EQUIPMENT) + .setText(", has lifelink")); + ability.addEffect(new GainAbilityAttachedEffect(commanderAbility, AttachmentType.EQUIPMENT) + .setText("and \"Other commanders you control get +2/+2 and have lifelink,\"")); + ability.addEffect(new AddCardSubtypeAttachedEffect(SubType.PERFORMER, AttachmentType.EQUIPMENT) + .setText("and is a Performer in addition to its other types")); + this.addAbility(ability); + + // Krishna -- Equip {3} + this.addAbility(new EquipAbility(3).withFlavorWord("Krishna")); + } + + private DancersChakrams(final DancersChakrams card) { + super(card); + } + + @Override + public DancersChakrams copy() { + return new DancersChakrams(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DawngladeRegent.java b/Mage.Sets/src/mage/cards/d/DawngladeRegent.java index fc56d256dda..432ad2431cf 100644 --- a/Mage.Sets/src/mage/cards/d/DawngladeRegent.java +++ b/Mage.Sets/src/mage/cards/d/DawngladeRegent.java @@ -14,6 +14,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; +import mage.filter.StaticFilters; import java.util.UUID; @@ -35,7 +36,7 @@ public final class DawngladeRegent extends CardImpl { // As long as you're the monarch, permanents you control have hexproof. this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( new GainAbilityControlledEffect( - HexproofAbility.getInstance(), Duration.WhileOnBattlefield + HexproofAbility.getInstance(), Duration.WhileOnBattlefield, StaticFilters.FILTER_PERMANENTS ), MonarchIsSourceControllerCondition.instance, "as long as you're the monarch, permanents you control have hexproof" ))); diff --git a/Mage.Sets/src/mage/cards/d/DawnsTruce.java b/Mage.Sets/src/mage/cards/d/DawnsTruce.java index ce370569793..27ad5fe692d 100644 --- a/Mage.Sets/src/mage/cards/d/DawnsTruce.java +++ b/Mage.Sets/src/mage/cards/d/DawnsTruce.java @@ -13,6 +13,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.GiftType; +import mage.filter.StaticFilters; import java.util.UUID; @@ -32,11 +33,12 @@ public final class DawnsTruce extends CardImpl { HexproofAbility.getInstance(), Duration.EndOfTurn ).setText("you")); this.getSpellAbility().addEffect(new GainAbilityControlledEffect( - HexproofAbility.getInstance(), Duration.EndOfTurn + HexproofAbility.getInstance(), Duration.EndOfTurn, StaticFilters.FILTER_PERMANENTS ).concatBy("and")); this.getSpellAbility().addEffect(new ConditionalOneShotEffect( new AddContinuousEffectToGame(new GainAbilityControlledEffect( - IndestructibleAbility.getInstance(), Duration.EndOfTurn + IndestructibleAbility.getInstance(), Duration.EndOfTurn, + StaticFilters.FILTER_PERMANENTS )), GiftWasPromisedCondition.TRUE, "if the gift was promised, " + "permanents you control also gain indestructible until end of turn" )); diff --git a/Mage.Sets/src/mage/cards/d/DayOfTheMoon.java b/Mage.Sets/src/mage/cards/d/DayOfTheMoon.java new file mode 100644 index 00000000000..249b14f173e --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DayOfTheMoon.java @@ -0,0 +1,111 @@ +package mage.cards.d; + +import mage.abilities.Ability; +import mage.abilities.common.SagaAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ChooseACardNameEffect; +import mage.abilities.effects.common.combat.GoadTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.choices.Choice; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SagaChapter; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.NamePredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.targetpointer.FixedTargets; +import mage.util.CardUtil; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author TheElk801 + */ +public final class DayOfTheMoon extends CardImpl { + + public DayOfTheMoon(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{R}"); + + this.subtype.add(SubType.SAGA); + + // (As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.) + SagaAbility sagaAbility = new SagaAbility(this); + + // I, II, III -- Choose a creature card name, then goad all creatures with a name chosen for Day of the Moon. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_I, SagaChapter.CHAPTER_III, new DayOfTheMoonEffect()); + this.addAbility(sagaAbility); + } + + private DayOfTheMoon(final DayOfTheMoon card) { + super(card); + } + + @Override + public DayOfTheMoon copy() { + return new DayOfTheMoon(this); + } +} + +class DayOfTheMoonEffect extends OneShotEffect { + + DayOfTheMoonEffect() { + super(Outcome.Benefit); + staticText = "choose a creature card name, then goad all creatures with a name chosen for {this}"; + } + + private DayOfTheMoonEffect(final DayOfTheMoonEffect effect) { + super(effect); + } + + @Override + public DayOfTheMoonEffect copy() { + return new DayOfTheMoonEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Choice cardChoice = ChooseACardNameEffect.TypeOfName.CREATURE_NAME.makeChoiceObject(); + player.choose(outcome, cardChoice, game); + String cardName = cardChoice.getChoice(); + List names = getOrSetValue(game, source); + names.add(cardName); + names.removeIf(Objects::nonNull); + if (names.isEmpty()) { + return true; + } + game.informPlayers( + CardUtil.getSourceLogName(game, source) + ": " + player.getName() + ", chosen name: [" + cardName + ']' + ); + Optional.ofNullable(source.getSourcePermanentIfItStillExists(game)) + .ifPresent(permanent -> permanent.addInfo( + "NAMED_CARD", CardUtil.addToolTipMarkTags( + "Chosen names: " + CardUtil.concatWithAnd(names) + ), game + )); + FilterPermanent filter = new FilterCreaturePermanent(); + filter.add(Predicates.or(names.stream().map(NamePredicate::new).collect(Collectors.toSet()))); + game.addEffect(new GoadTargetEffect().setTargetPointer(new FixedTargets( + game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source, game), game + )), source); + return true; + } + + private static List getOrSetValue(Game game, Ability source) { + String key = "DayOfTheMoon_" + source.getControllerId() + '_' + source.getSourceObjectZoneChangeCounter(); + List list = (List) game.getState().getValue(key); + if (list != null) { + return list; + } + return game.getState().setValue(key, new ArrayList<>()); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DeadeyeHarpooner.java b/Mage.Sets/src/mage/cards/d/DeadeyeHarpooner.java index f0038f030ef..9decd10ada3 100644 --- a/Mage.Sets/src/mage/cards/d/DeadeyeHarpooner.java +++ b/Mage.Sets/src/mage/cards/d/DeadeyeHarpooner.java @@ -4,7 +4,6 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.condition.common.RevoltCondition; -import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.common.DestroyTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -40,14 +39,10 @@ public final class DeadeyeHarpooner extends CardImpl { this.toughness = new MageInt(2); // Revolt — When Deadeye Harpooner enters the battlefield, if a permanent you controlled left the battlefield this turn, destroy target tapped creature an opponent controls. - Ability ability = new ConditionalInterveningIfTriggeredAbility( - new EntersBattlefieldTriggeredAbility(new DestroyTargetEffect(), false), - RevoltCondition.instance, "When {this} enters, if a permanent you controlled " + - "left the battlefield this turn, destroy target tapped creature an opponent controls." - ); - ability.setAbilityWord(AbilityWord.REVOLT); + Ability ability = new EntersBattlefieldTriggeredAbility(new DestroyTargetEffect()) + .withInterveningIf(RevoltCondition.instance); ability.addTarget(new TargetPermanent(filter)); - this.addAbility(ability.addHint(RevoltCondition.getHint()), new RevoltWatcher()); + this.addAbility(ability.setAbilityWord(AbilityWord.REVOLT).addHint(RevoltCondition.getHint()), new RevoltWatcher()); } private DeadeyeHarpooner(final DeadeyeHarpooner card) { diff --git a/Mage.Sets/src/mage/cards/d/DeadlyBrew.java b/Mage.Sets/src/mage/cards/d/DeadlyBrew.java index 32ea8267ef7..6f01f552cc6 100644 --- a/Mage.Sets/src/mage/cards/d/DeadlyBrew.java +++ b/Mage.Sets/src/mage/cards/d/DeadlyBrew.java @@ -53,7 +53,7 @@ class DeadlyBrewEffect extends OneShotEffect { DeadlyBrewEffect() { super(Outcome.Benefit); - staticText = "each player sacrifices a creature or planeswalker. If you sacrificed a permanent this way, " + + staticText = "each player sacrifices a creature or planeswalker of their choice. If you sacrificed a permanent this way, " + "you may return another permanent card from your graveyard to your hand"; } diff --git a/Mage.Sets/src/mage/cards/d/DeadlyEmbrace.java b/Mage.Sets/src/mage/cards/d/DeadlyEmbrace.java new file mode 100644 index 00000000000..5c14c7e619e --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DeadlyEmbrace.java @@ -0,0 +1,38 @@ +package mage.cards.d; + +import mage.abilities.dynamicvalue.common.CreaturesDiedThisTurnCount; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.hint.common.CreaturesDiedThisTurnHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.target.common.TargetOpponentsCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DeadlyEmbrace extends CardImpl { + + public DeadlyEmbrace(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{B}{B}"); + + // Destroy target creature an opponent controls. Then draw a card for each creature that died this turn. + this.getSpellAbility().addEffect(new DestroyTargetEffect()); + this.getSpellAbility().addTarget(new TargetOpponentsCreaturePermanent()); + this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(CreaturesDiedThisTurnCount.instance) + .setText("Then draw a card for each creature that died this turn")); + this.getSpellAbility().addHint(CreaturesDiedThisTurnHint.instance); + } + + private DeadlyEmbrace(final DeadlyEmbrace card) { + super(card); + } + + @Override + public DeadlyEmbrace copy() { + return new DeadlyEmbrace(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/Decommission.java b/Mage.Sets/src/mage/cards/d/Decommission.java index 147fe2d959a..4e3bba20f53 100644 --- a/Mage.Sets/src/mage/cards/d/Decommission.java +++ b/Mage.Sets/src/mage/cards/d/Decommission.java @@ -7,6 +7,7 @@ import mage.abilities.effects.common.DestroyTargetEffect; import mage.abilities.effects.common.GainLifeEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.constants.AbilityWord; import mage.constants.CardType; import mage.filter.StaticFilters; import mage.target.TargetPermanent; @@ -15,7 +16,6 @@ import mage.watchers.common.RevoltWatcher; import java.util.UUID; /** - * * @author emerald000 */ public final class Decommission extends CardImpl { @@ -28,7 +28,10 @@ public final class Decommission extends CardImpl { this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_ARTIFACT_OR_ENCHANTMENT)); // Revolt — If a permanent you controlled left the battlefield this turn, you gain 3 life. - this.getSpellAbility().addEffect(new ConditionalOneShotEffect(new GainLifeEffect(3), RevoltCondition.instance, "
Revolt — If a permanent you controlled left the battlefield this turn, you gain 3 life.")); + this.getSpellAbility().addEffect(new ConditionalOneShotEffect( + new GainLifeEffect(3), RevoltCondition.instance, "
" + AbilityWord.REVOLT.formatWord() + + "If a permanent you controlled left the battlefield this turn, you gain 3 life." + )); this.getSpellAbility().addWatcher(new RevoltWatcher()); this.getSpellAbility().addHint(RevoltCondition.getHint()); } diff --git a/Mage.Sets/src/mage/cards/d/Delay.java b/Mage.Sets/src/mage/cards/d/Delay.java index ee21e510be2..f00b81d9b47 100644 --- a/Mage.Sets/src/mage/cards/d/Delay.java +++ b/Mage.Sets/src/mage/cards/d/Delay.java @@ -1,28 +1,21 @@ package mage.cards.d; -import java.util.UUID; -import mage.MageObjectReference; import mage.abilities.Ability; -import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.CounterTargetWithReplacementEffect; -import mage.abilities.effects.common.continuous.GainSuspendEffect; import mage.abilities.keyword.SuspendAbility; -import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.PutCards; -import mage.constants.Zone; -import mage.counters.CounterType; import mage.game.Game; import mage.game.stack.Spell; import mage.players.Player; import mage.target.TargetSpell; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class Delay extends CardImpl { @@ -49,7 +42,8 @@ class DelayEffect extends OneShotEffect { DelayEffect() { super(Outcome.Benefit); - this.staticText = "Counter target spell. If the spell is countered this way, exile it with three time counters on it instead of putting it into its owner's graveyard. If it doesn't have suspend, it gains suspend"; + this.staticText = "counter target spell. If the spell is countered this way, exile it with three time counters " + + "on it instead of putting it into its owner's graveyard. If it doesn't have suspend, it gains suspend"; } private DelayEffect(final DelayEffect effect) { @@ -65,23 +59,9 @@ class DelayEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); Spell spell = game.getStack().getSpell(getTargetPointer().getFirst(game, source)); - if (controller != null && spell != null) { - Effect effect = new CounterTargetWithReplacementEffect(PutCards.EXILED); - effect.setTargetPointer(this.getTargetPointer().copy()); - Card card = spell.getMainCard(); - if (card != null && effect.apply(game, source) && game.getState().getZone(card.getId()) == Zone.EXILED) { - boolean hasSuspend = card.getAbilities(game).containsClass(SuspendAbility.class); - UUID exileId = SuspendAbility.getSuspendExileId(controller.getId(), game); - if (controller.moveCardToExileWithInfo(card, exileId, "Suspended cards of " + controller.getName(), source, game, Zone.HAND, true)) { - card.addCounters(CounterType.TIME.createInstance(3), source.getControllerId(), source, game); - if (!hasSuspend) { - game.addEffect(new GainSuspendEffect(new MageObjectReference(card, game)), source); - } - game.informPlayers(controller.getLogName() + " suspends 3 - " + card.getName()); - } - } - return true; - } - return false; + return controller != null + && spell != null + && game.getStack().counter(spell.getId(), source, game, PutCards.EXILED) + && SuspendAbility.addTimeCountersAndSuspend(spell.getMainCard(), 3, source, game); } } diff --git a/Mage.Sets/src/mage/cards/d/DemonicCovenant.java b/Mage.Sets/src/mage/cards/d/DemonicCovenant.java new file mode 100644 index 00000000000..02be019387c --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DemonicCovenant.java @@ -0,0 +1,129 @@ +package mage.cards.d; + +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.LoseLifeSourceControllerEffect; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.game.Controllable; +import mage.game.Game; +import mage.game.events.DefenderAttackedEvent; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.DemonToken; +import mage.players.Player; +import mage.util.CardUtil; + +import java.util.Arrays; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DemonicCovenant extends CardImpl { + + public DemonicCovenant(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.KINDRED, CardType.ENCHANTMENT}, "{4}{B}{B}"); + + this.subtype.add(SubType.DEMON); + + // Whenever one or more Demons you control attack a player, you draw a card and lose 1 life. + this.addAbility(new DemonicCovenantTriggeredAbility()); + + // At the beginning of your end step, create a 5/5 black Demon creature token with flying, then mill two cards. If two cards that share all their card types were milled this way, sacrifice Demonic Covenant. + Ability ability = new BeginningOfEndStepTriggeredAbility(new CreateTokenEffect(new DemonToken())); + ability.addEffect(new DemonicCovenantEffect()); + this.addAbility(ability); + } + + private DemonicCovenant(final DemonicCovenant card) { + super(card); + } + + @Override + public DemonicCovenant copy() { + return new DemonicCovenant(this); + } +} + +class DemonicCovenantTriggeredAbility extends TriggeredAbilityImpl { + + DemonicCovenantTriggeredAbility() { + super(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1, true)); + this.addEffect(new LoseLifeSourceControllerEffect(1).setText("and lose 1 life")); + this.setTriggerPhrase("Whenever one or more Demons you control attack a player, "); + } + + private DemonicCovenantTriggeredAbility(final DemonicCovenantTriggeredAbility ability) { + super(ability); + } + + @Override + public DemonicCovenantTriggeredAbility copy() { + return new DemonicCovenantTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DEFENDER_ATTACKED; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return game.getPlayer(event.getTargetId()) != null + && ((DefenderAttackedEvent) event) + .getAttackers(game) + .stream() + .filter(permanent -> permanent.hasSubtype(SubType.DEMON, game)) + .map(Controllable::getControllerId) + .anyMatch(this::isControlledBy); + } +} + +class DemonicCovenantEffect extends OneShotEffect { + + DemonicCovenantEffect() { + super(Outcome.Benefit); + staticText = ", then mill two cards. If two cards that share " + + "all their card types were milled this way, sacrifice {this}"; + } + + private DemonicCovenantEffect(final DemonicCovenantEffect effect) { + super(effect); + } + + @Override + public DemonicCovenantEffect copy() { + return new DemonicCovenantEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Cards cards = player.millCards(2, source, game); + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + return cards.size() >= 2 + && permanent != null + && CardUtil + .checkAnyPairs( + cards.getCards(game), + (c1, c2) -> Arrays + .stream(CardType.values()) + .allMatch(cardType -> c1.getCardType(game).contains(cardType) + == c2.getCardType(game).contains(cardType)) + ) + && permanent.sacrifice(source, game); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DepalaPilotExemplar.java b/Mage.Sets/src/mage/cards/d/DepalaPilotExemplar.java index 126338d0e63..1af7a79cc1e 100644 --- a/Mage.Sets/src/mage/cards/d/DepalaPilotExemplar.java +++ b/Mage.Sets/src/mage/cards/d/DepalaPilotExemplar.java @@ -86,7 +86,7 @@ class DepalaPilotExemplarEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { ManaCosts cost = new ManaCostsImpl<>("{X}"); - int xValue = controller.announceXMana(0, Integer.MAX_VALUE, "Choose the amount of mana to pay", game, source); + int xValue = controller.announceX(0, Integer.MAX_VALUE, "Announce the value for {X} (pay to reveal)", game, source, true); cost.add(new GenericManaCost(xValue)); if (cost.pay(source, game, source, source.getControllerId(), false) && xValue > 0) { new RevealLibraryPutIntoHandEffect(xValue, filter, Zone.LIBRARY, false).apply(game, source); diff --git a/Mage.Sets/src/mage/cards/d/DesecrationDemon.java b/Mage.Sets/src/mage/cards/d/DesecrationDemon.java index a2f0c0fec99..222d5b35ad9 100644 --- a/Mage.Sets/src/mage/cards/d/DesecrationDemon.java +++ b/Mage.Sets/src/mage/cards/d/DesecrationDemon.java @@ -54,7 +54,7 @@ public final class DesecrationDemon extends CardImpl { class DesecrationDemonEffect extends OneShotEffect { DesecrationDemonEffect() { super(Outcome.BoostCreature); - staticText = "any opponent may sacrifice a creature. If a player does, tap {this} and put a +1/+1 counter on it"; + staticText = "any opponent may sacrifice a creature of their choice. If a player does, tap {this} and put a +1/+1 counter on it"; } private DesecrationDemonEffect(final DesecrationDemonEffect effect) { diff --git a/Mage.Sets/src/mage/cards/d/DesperateMeasures.java b/Mage.Sets/src/mage/cards/d/DesperateMeasures.java index 446b737329c..4be289078eb 100644 --- a/Mage.Sets/src/mage/cards/d/DesperateMeasures.java +++ b/Mage.Sets/src/mage/cards/d/DesperateMeasures.java @@ -9,6 +9,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SetTargetPointer; +import mage.target.common.TargetCreaturePermanent; import java.util.UUID; @@ -28,6 +29,7 @@ public final class DesperateMeasures extends CardImpl { SetTargetPointer.NONE, true ), true )); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); } private DesperateMeasures(final DesperateMeasures card) { diff --git a/Mage.Sets/src/mage/cards/d/DevourFlesh.java b/Mage.Sets/src/mage/cards/d/DevourFlesh.java index 638d0b1b144..c5bbeccc4e1 100644 --- a/Mage.Sets/src/mage/cards/d/DevourFlesh.java +++ b/Mage.Sets/src/mage/cards/d/DevourFlesh.java @@ -64,9 +64,7 @@ class DevourFleshSacrificeEffect extends OneShotEffect { } if (game.getBattlefield().count(TargetSacrifice.makeFilter(StaticFilters.FILTER_PERMANENT_CREATURE), player.getId(), source, game) > 0) { Target target = new TargetSacrifice(StaticFilters.FILTER_PERMANENT_CREATURE); - while (player.canRespond() && !target.isChosen(game) && target.canChoose(player.getId(), source, game)) { - player.choose(Outcome.Sacrifice, target, source, game); - } + player.choose(Outcome.Sacrifice, target, source, game); Permanent permanent = game.getPermanent(target.getFirstTarget()); if (permanent != null) { int gainLife = permanent.getToughness().getValue(); diff --git a/Mage.Sets/src/mage/cards/d/DieYoung.java b/Mage.Sets/src/mage/cards/d/DieYoung.java index 50567d87692..fedc892e9a0 100644 --- a/Mage.Sets/src/mage/cards/d/DieYoung.java +++ b/Mage.Sets/src/mage/cards/d/DieYoung.java @@ -67,7 +67,7 @@ class DieYoungEffect extends OneShotEffect { if (controller != null) { new GetEnergyCountersControllerEffect(2).apply(game, source); int max = controller.getCountersCount(CounterType.ENERGY); - int numberToPayed = controller.getAmount(0, max, "How many energy counters do you like to pay? (maximum = " + max + ')', game); + int numberToPayed = controller.getAmount(0, max, "How many energy counters do you like to pay? (maximum = " + max + ')', source, game); if (numberToPayed > 0) { Cost cost = new PayEnergyCost(numberToPayed); if (cost.pay(source, game, source, source.getControllerId(), true)) { diff --git a/Mage.Sets/src/mage/cards/d/DiminishingReturns.java b/Mage.Sets/src/mage/cards/d/DiminishingReturns.java index d7eb026335d..213a5864f6e 100644 --- a/Mage.Sets/src/mage/cards/d/DiminishingReturns.java +++ b/Mage.Sets/src/mage/cards/d/DiminishingReturns.java @@ -57,7 +57,7 @@ class DiminishingReturnsEffect extends OneShotEffect { for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) { Player player = game.getPlayer(playerId); if (player != null) { - int amount = player.getAmount(0, 7, "How many cards to draw (up to 7)?", game); + int amount = player.getAmount(0, 7, "How many cards to draw (up to 7)?", source, game); player.drawCards(amount, source, game); } } diff --git a/Mage.Sets/src/mage/cards/d/DivineReckoning.java b/Mage.Sets/src/mage/cards/d/DivineReckoning.java index b16bfdbc3eb..1f16b7df8e1 100644 --- a/Mage.Sets/src/mage/cards/d/DivineReckoning.java +++ b/Mage.Sets/src/mage/cards/d/DivineReckoning.java @@ -67,10 +67,7 @@ class DivineReckoningEffect extends OneShotEffect { Player player = game.getPlayer(playerId); if (player != null) { Target target = new TargetControlledPermanent(1, 1, new FilterControlledCreaturePermanent(), true); - if (target.canChoose(player.getId(), source, game)) { - while (player.canRespond() && !target.isChosen(game) && target.canChoose(player.getId(), source, game)) { - player.chooseTarget(Outcome.Benefit, target, source, game); - } + if (player.chooseTarget(Outcome.Benefit, target, source, game)) { Permanent permanent = game.getPermanent(target.getFirstTarget()); if (permanent != null) { chosen.add(permanent); diff --git a/Mage.Sets/src/mage/cards/d/DragoonsLance.java b/Mage.Sets/src/mage/cards/d/DragoonsLance.java new file mode 100644 index 00000000000..6e263aea635 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DragoonsLance.java @@ -0,0 +1,59 @@ +package mage.cards.d; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.MyTurnCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.common.continuous.AddCardSubtypeAttachedEffect; +import mage.abilities.effects.common.continuous.BoostEquippedEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.keyword.EquipAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.JobSelectAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AttachmentType; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DragoonsLance extends CardImpl { + + public DragoonsLance(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{W}"); + + this.subtype.add(SubType.EQUIPMENT); + + // Job select + this.addAbility(new JobSelectAbility()); + + // Equipped creature gets +1/+0 and is a Knight in addition to its other types. + Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(1, 0)); + ability.addEffect(new AddCardSubtypeAttachedEffect( + SubType.KNIGHT, AttachmentType.EQUIPMENT + ).setText("and is a Knight in addition to its other types")); + this.addAbility(ability); + + // During your turn, equipped creature has flying. + this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( + new GainAbilityAttachedEffect(FlyingAbility.getInstance(), AttachmentType.EQUIPMENT), + MyTurnCondition.instance, "during your turn, equipped creature has flying" + ))); + + // Gae Bolg -- Equip {4} + this.addAbility(new EquipAbility(4).withFlavorWord("Gae Bolg")); + } + + private DragoonsLance(final DragoonsLance card) { + super(card); + } + + @Override + public DragoonsLance copy() { + return new DragoonsLance(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DragoonsWyvern.java b/Mage.Sets/src/mage/cards/d/DragoonsWyvern.java new file mode 100644 index 00000000000..c01e03be5f6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DragoonsWyvern.java @@ -0,0 +1,42 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.game.permanent.token.HeroToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DragoonsWyvern extends CardImpl { + + public DragoonsWyvern(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); + + this.subtype.add(SubType.DRAKE); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When this creature enters, create a 1/1 colorless Hero creature token. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new HeroToken()))); + } + + private DragoonsWyvern(final DragoonsWyvern card) { + super(card); + } + + @Override + public DragoonsWyvern copy() { + return new DragoonsWyvern(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DreamsOfLaguna.java b/Mage.Sets/src/mage/cards/d/DreamsOfLaguna.java new file mode 100644 index 00000000000..69a4dcfe975 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DreamsOfLaguna.java @@ -0,0 +1,37 @@ +package mage.cards.d; + +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.keyword.SurveilEffect; +import mage.abilities.keyword.FlashbackAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DreamsOfLaguna extends CardImpl { + + public DreamsOfLaguna(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}"); + + // Surveil 1, then draw a card. + this.getSpellAbility().addEffect(new SurveilEffect(1, false)); + this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(1).concatBy(", then")); + + // Flashback {3}{U} + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{3}{U}"))); + } + + private DreamsOfLaguna(final DreamsOfLaguna card) { + super(card); + } + + @Override + public DreamsOfLaguna copy() { + return new DreamsOfLaguna(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DrivenDespair.java b/Mage.Sets/src/mage/cards/d/DrivenDespair.java index 1b10535809d..abbd95fab88 100644 --- a/Mage.Sets/src/mage/cards/d/DrivenDespair.java +++ b/Mage.Sets/src/mage/cards/d/DrivenDespair.java @@ -1,8 +1,6 @@ package mage.cards.d; -import java.util.UUID; -import mage.abilities.TriggeredAbility; import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; @@ -17,8 +15,9 @@ import mage.constants.Duration; import mage.constants.SpellAbilityType; import mage.filter.StaticFilters; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class DrivenDespair extends SplitCard { @@ -27,26 +26,27 @@ public final class DrivenDespair extends SplitCard { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, new CardType[]{CardType.SORCERY}, "{1}{G}", "{1}{B}", SpellAbilityType.SPLIT_AFTERMATH); // Until end of turn, creatures you control gain trample and "Whenever this creature deals combat damage to a player, draw a card." - getLeftHalfCard().getSpellAbility().addEffect(new GainAbilityControlledEffect( - TrampleAbility.getInstance(), Duration.EndOfTurn, StaticFilters.FILTER_PERMANENT_CREATURES) - .setText("Until end of turn, creatures you control gain trample")); - TriggeredAbility ability = new DealsCombatDamageToAPlayerTriggeredAbility(new DrawCardSourceControllerEffect(1), false); - getLeftHalfCard().getSpellAbility().addEffect(new GainAbilityControlledEffect(ability, Duration.EndOfTurn) - .setText("\"Whenever this creature deals combat damage to a player, draw a card.\"") - .concatBy("and")); + this.getLeftHalfCard().getSpellAbility().addEffect(new GainAbilityControlledEffect( + TrampleAbility.getInstance(), Duration.EndOfTurn, StaticFilters.FILTER_PERMANENT_CREATURES + ).setText("Until end of turn, creatures you control gain trample")); + this.getLeftHalfCard().getSpellAbility().addEffect(new GainAbilityControlledEffect( + new DealsCombatDamageToAPlayerTriggeredAbility(new DrawCardSourceControllerEffect(1), false), + Duration.EndOfTurn, StaticFilters.FILTER_PERMANENT_CREATURES + ).setText("\"Whenever this creature deals combat damage to a player, draw a card.\"").concatBy("and")); // Despair {1}{B} // Sorcery // Aftermath - getRightHalfCard().addAbility(new AftermathAbility().setRuleAtTheTop(true)); + this.getRightHalfCard().addAbility(new AftermathAbility().setRuleAtTheTop(true)); // Until end of turn, creatures you control gain menace and "Whenever this creature deals combat damage to a player, that player discards a card." - getRightHalfCard().getSpellAbility().addEffect(new GainAbilityControlledEffect( - new MenaceAbility(), Duration.EndOfTurn, StaticFilters.FILTER_PERMANENT_CREATURES) - .setText("Until end of turn, creatures you control gain menace")); - ability = new DealsCombatDamageToAPlayerTriggeredAbility(new DiscardTargetEffect(1), false, true); - getRightHalfCard().getSpellAbility().addEffect(new GainAbilityControlledEffect(ability, Duration.EndOfTurn, StaticFilters.FILTER_CONTROLLED_CREATURES) - .setText("\"Whenever this creature deals combat damage to a player, that player discards a card.\"") - .concatBy("and")); + this.getRightHalfCard().getSpellAbility().addEffect(new GainAbilityControlledEffect( + new MenaceAbility(), Duration.EndOfTurn, StaticFilters.FILTER_PERMANENT_CREATURES + ).setText("Until end of turn, creatures you control gain menace")); + this.getRightHalfCard().getSpellAbility().addEffect(new GainAbilityControlledEffect( + new DealsCombatDamageToAPlayerTriggeredAbility( + new DiscardTargetEffect(1), false, true + ), Duration.EndOfTurn, StaticFilters.FILTER_PERMANENT_CREATURES + ).setText("\"Whenever this creature deals combat damage to a player, that player discards a card.\"").concatBy("and")); } diff --git a/Mage.Sets/src/mage/cards/e/ElenaTurkRecruit.java b/Mage.Sets/src/mage/cards/e/ElenaTurkRecruit.java new file mode 100644 index 00000000000..c5fbea1f82f --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/ElenaTurkRecruit.java @@ -0,0 +1,64 @@ +package mage.cards.e; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.FilterCard; +import mage.filter.FilterSpell; +import mage.filter.common.FilterHistoricCard; +import mage.filter.common.FilterHistoricSpell; +import mage.filter.predicate.Predicates; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ElenaTurkRecruit extends CardImpl { + + private static final FilterCard filter = new FilterHistoricCard("non-Assassin historic card from your graveyard"); + private static final FilterSpell filter2 = new FilterHistoricSpell(); + + static { + filter.add(Predicates.not(SubType.ASSASSIN.getPredicate())); + } + + public ElenaTurkRecruit(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ASSASSIN); + this.power = new MageInt(1); + this.toughness = new MageInt(4); + + // When Elena enters, return target non-Assassin historic card from your graveyard to your hand. + Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnFromGraveyardToHandTargetEffect()); + ability.addTarget(new TargetCardInYourGraveyard(filter)); + this.addAbility(ability); + + // Whenever you cast a historic spell, put a +1/+1 counter on Elena. + this.addAbility(new SpellCastControllerTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), filter2, false + )); + } + + private ElenaTurkRecruit(final ElenaTurkRecruit card) { + super(card); + } + + @Override + public ElenaTurkRecruit copy() { + return new ElenaTurkRecruit(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/ElendaAndAzor.java b/Mage.Sets/src/mage/cards/e/ElendaAndAzor.java index 12d6d51fff6..5affdf40281 100644 --- a/Mage.Sets/src/mage/cards/e/ElendaAndAzor.java +++ b/Mage.Sets/src/mage/cards/e/ElendaAndAzor.java @@ -80,7 +80,7 @@ class ElendaAndAzorEffect extends OneShotEffect { if (controller != null) { ManaCosts cost = new ManaCostsImpl<>("{X}{W}{U}{B}"); if (controller.chooseUse(Outcome.Damage, "Pay " + cost.getText() + "? If you do, draw X cards.", source, game)) { - int costX = controller.announceXMana(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source); + int costX = controller.announceX(0, Integer.MAX_VALUE, "Announce the value for {X} (pay to draw)", game, source, true); cost.add(new GenericManaCost(costX)); if (cost.pay(source, game, source, source.getControllerId(), false, null)) { controller.resetStoredBookmark(game); // otherwise you can undo the payment diff --git a/Mage.Sets/src/mage/cards/e/EliminateTheCompetition.java b/Mage.Sets/src/mage/cards/e/EliminateTheCompetition.java index 7659d17b37d..2e474215c33 100644 --- a/Mage.Sets/src/mage/cards/e/EliminateTheCompetition.java +++ b/Mage.Sets/src/mage/cards/e/EliminateTheCompetition.java @@ -22,7 +22,7 @@ public final class EliminateTheCompetition extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{B}"); // As an additional cost to cast Eliminate the Competition, sacrifice X creatures. - this.getSpellAbility().addCost(new SacrificeXTargetCost(new FilterControlledCreaturePermanent("creatures"), true)); + this.getSpellAbility().addCost(new SacrificeXTargetCost(new FilterControlledCreaturePermanent("creatures"), false)); // Destroy X target creatures. Effect effect = new DestroyTargetEffect(); diff --git a/Mage.Sets/src/mage/cards/e/EmetSelchUnsundered.java b/Mage.Sets/src/mage/cards/e/EmetSelchUnsundered.java new file mode 100644 index 00000000000..c2f8e872ee5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EmetSelchUnsundered.java @@ -0,0 +1,56 @@ +package mage.cards.e; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.condition.common.CardsInControllerGraveyardCondition; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility; +import mage.abilities.effects.common.DrawDiscardControllerEffect; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.keyword.TransformAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; + +/** + * @author balazskristof + */ +public final class EmetSelchUnsundered extends CardImpl { + + public EmetSelchUnsundered(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.ELDER); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + this.secondSideCardClazz = mage.cards.h.HadesSorcererOfEld.class; + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Whenever Emet-Selch enters or attacks, draw a card, then discard a card. + this.addAbility(new EntersBattlefieldOrAttacksSourceTriggeredAbility(new DrawDiscardControllerEffect(1, 1))); + + // At the beginning of your upkeep, if there are fourteen or more cards in your graveyard, you may transform Emet-Selch. + this.addAbility(new BeginningOfUpkeepTriggeredAbility( + new TransformSourceEffect(), + true + ).withInterveningIf(new CardsInControllerGraveyardCondition(14))); + this.addAbility(new TransformAbility()); + } + + private EmetSelchUnsundered(final EmetSelchUnsundered card) { + super(card); + } + + @Override + public EmetSelchUnsundered copy() { + return new EmetSelchUnsundered(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/EmryLurkerOfTheLoch.java b/Mage.Sets/src/mage/cards/e/EmryLurkerOfTheLoch.java index f3d3a433be1..79ff36697d6 100644 --- a/Mage.Sets/src/mage/cards/e/EmryLurkerOfTheLoch.java +++ b/Mage.Sets/src/mage/cards/e/EmryLurkerOfTheLoch.java @@ -4,13 +4,10 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; -import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.common.TapSourceCost; -import mage.abilities.dynamicvalue.common.ArtifactYouControlCount; import mage.abilities.effects.AsThoughEffectImpl; import mage.abilities.effects.common.MillCardsControllerEffect; -import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; -import mage.abilities.hint.common.ArtifactYouControlHint; +import mage.abilities.keyword.AffinityForArtifactsAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; @@ -35,14 +32,10 @@ public final class EmryLurkerOfTheLoch extends CardImpl { this.toughness = new MageInt(2); // This spell costs {1} less to cast for each artifact you control. - this.addAbility(new SimpleStaticAbility(Zone.ALL, - new SpellCostReductionForEachSourceEffect(1, ArtifactYouControlCount.instance) - ).addHint(ArtifactYouControlHint.instance)); + this.addAbility(new AffinityForArtifactsAbility()); // When Emry, Lurker of the Loch enters the battlefield, put the top four cards of your library into your graveyard. - this.addAbility(new EntersBattlefieldTriggeredAbility( - new MillCardsControllerEffect(4) - )); + this.addAbility(new EntersBattlefieldTriggeredAbility(new MillCardsControllerEffect(4))); // {T}: Choose target artifact card in your graveyard. You may cast that card this turn. Ability ability = new SimpleActivatedAbility(new EmryLurkerOfTheLochPlayEffect(), new TapSourceCost()); @@ -84,15 +77,14 @@ class EmryLurkerOfTheLochPlayEffect extends AsThoughEffectImpl { @Override public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { UUID targetId = getTargetPointer().getFirst(game, source); - if (targetId != null) { - return targetId.equals(objectId) - && source.isControlledBy(affectedControllerId) - && Zone.GRAVEYARD == game.getState().getZone(objectId) - && !game.getCard(targetId).isLand(game); - } else { + if (targetId == null) { // the target card has changed zone meanwhile, so the effect is no longer needed discard(); return false; } + return targetId.equals(objectId) + && source.isControlledBy(affectedControllerId) + && Zone.GRAVEYARD == game.getState().getZone(objectId) + && !game.getCard(targetId).isLand(game); } } diff --git a/Mage.Sets/src/mage/cards/e/EnsnaredByTheMara.java b/Mage.Sets/src/mage/cards/e/EnsnaredByTheMara.java index 180cda0c375..1da6eb431b6 100644 --- a/Mage.Sets/src/mage/cards/e/EnsnaredByTheMara.java +++ b/Mage.Sets/src/mage/cards/e/EnsnaredByTheMara.java @@ -1,33 +1,34 @@ package mage.cards.e; -import java.util.UUID; +import mage.MageObject; import mage.abilities.Ability; -import mage.abilities.effects.OneShotEffect; -import mage.cards.Card; -import mage.cards.Cards; -import mage.cards.CardImpl; -import mage.cards.CardsImpl; -import mage.cards.CardSetInfo; +import mage.abilities.effects.common.FaceVillainousChoiceOpponentsEffect; +import mage.cards.*; import mage.choices.FaceVillainousChoice; import mage.choices.VillainousChoice; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; import mage.game.Game; import mage.players.Player; -import mage.MageObject; import mage.util.CardUtil; +import java.util.UUID; + /** - * * @author padfoot */ public final class EnsnaredByTheMara extends CardImpl { + private static final FaceVillainousChoice choice = new FaceVillainousChoice( + Outcome.PlayForFree, new EnsnaredByTheMaraFirstChoice(), new EnsnaredByTheMaraSecondChoice() + ); + public EnsnaredByTheMara(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{R}{R}"); - // Each opponent faces a villainous choice -- They exile cards from the top of their library until they exile a nonland card, then you may cast that card without paying its mana cost, or that player exiles the top four cards of their library and Ensnared by the Mara deals damage equal to the total mana value of those exiled cards to that player. - this.getSpellAbility().addEffect(new EnsnaredByTheMaraEffect()); + this.getSpellAbility().addEffect(new FaceVillainousChoiceOpponentsEffect(choice)); } private EnsnaredByTheMara(final EnsnaredByTheMara card) { @@ -40,45 +41,15 @@ public final class EnsnaredByTheMara extends CardImpl { } } -class EnsnaredByTheMaraEffect extends OneShotEffect { - - private static final FaceVillainousChoice choice = new FaceVillainousChoice( - Outcome.PlayForFree, new EnsnaredByTheMaraFirstChoice(), new EnsnaredByTheMaraSecondChoice() - ); - - EnsnaredByTheMaraEffect() { - super(Outcome.Benefit); - staticText = "each opponent " + choice.generateRule(); - } - - private EnsnaredByTheMaraEffect(final EnsnaredByTheMaraEffect effect) { - super(effect); - } - - @Override - public EnsnaredByTheMaraEffect copy() { - return new EnsnaredByTheMaraEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - for (UUID playerId : game.getOpponents(source.getControllerId())) { - Player player = game.getPlayer(playerId); - choice.faceChoice(player, game, source); - } - return true; - } -} - class EnsnaredByTheMaraFirstChoice extends VillainousChoice { EnsnaredByTheMaraFirstChoice() { - super("They exile cards from the top of their library until they exile a nonland card, then you may cast that card without paying its mana cost","Exile a card for {controller} to cast for free"); + super("They exile cards from the top of their library until they exile a nonland card, then you may cast that card without paying its mana cost", "Exile a card for {controller} to cast for free"); } @Override public boolean doChoice(Player player, Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - for (Card card : player.getLibrary().getCards(game)) { + for (Card card : player.getLibrary().getCards(game)) { player.moveCards(card, Zone.EXILED, source, game); if (!card.isLand(game)) { CardUtil.castSpellWithAttributesForFree(controller, source, game, card); @@ -91,20 +62,19 @@ class EnsnaredByTheMaraFirstChoice extends VillainousChoice { class EnsnaredByTheMaraSecondChoice extends VillainousChoice { EnsnaredByTheMaraSecondChoice() { - super("that player exiles the top four cards of their library and {this} deals damage equal to the total mana value of those exiled cards to that player","exile four cards and take damage"); + super("that player exiles the top four cards of their library and {this} deals damage equal to the total mana value of those exiled cards to that player", "exile four cards and take damage"); } @Override public boolean doChoice(Player player, Game game, Ability source) { - int totalManaValue = 0; - Cards cards = new CardsImpl(player.getLibrary().getTopCards(game, 4)); - player.moveCards(cards, Zone.EXILED, source, game); - totalManaValue = cards - .getCards(game) - .stream() - .mapToInt(MageObject::getManaValue) - .sum(); - player.damage(totalManaValue, source, game); - return true; + Cards cards = new CardsImpl(player.getLibrary().getTopCards(game, 4)); + player.moveCards(cards, Zone.EXILED, source, game); + int totalManaValue = cards + .getCards(game) + .stream() + .mapToInt(MageObject::getManaValue) + .sum(); + player.damage(totalManaValue, source, game); + return true; } } diff --git a/Mage.Sets/src/mage/cards/e/EntrapmentManeuver.java b/Mage.Sets/src/mage/cards/e/EntrapmentManeuver.java index 87f020ada89..686e3311a03 100644 --- a/Mage.Sets/src/mage/cards/e/EntrapmentManeuver.java +++ b/Mage.Sets/src/mage/cards/e/EntrapmentManeuver.java @@ -66,9 +66,7 @@ class EntrapmentManeuverSacrificeEffect extends OneShotEffect { } if (game.getBattlefield().count(TargetSacrifice.makeFilter(StaticFilters.FILTER_ATTACKING_CREATURE), player.getId(), source, game) > 0) { Target target = new TargetSacrifice(StaticFilters.FILTER_ATTACKING_CREATURE); - while (player.canRespond() && !target.isChosen(game) && target.canChoose(player.getId(), source, game)) { - player.choose(Outcome.Sacrifice, target, source, game); - } + player.choose(Outcome.Sacrifice, target, source, game); Permanent permanent = game.getPermanent(target.getFirstTarget()); if (permanent != null) { int amount = permanent.getToughness().getValue(); diff --git a/Mage.Sets/src/mage/cards/e/EriettesTemptingApple.java b/Mage.Sets/src/mage/cards/e/EriettesTemptingApple.java index 9711abc1f73..d659dd6e01f 100644 --- a/Mage.Sets/src/mage/cards/e/EriettesTemptingApple.java +++ b/Mage.Sets/src/mage/cards/e/EriettesTemptingApple.java @@ -42,7 +42,7 @@ public final class EriettesTemptingApple extends CardImpl { this.addAbility(ability); // {2}, {T}, Sacrifice Eriette's Tempting Apple: You gain 3 life. - this.addAbility(new FoodAbility(true)); + this.addAbility(new FoodAbility()); // {2}, {T}, Sacrifice Eriette's Tempting Apple: Target opponent loses 3 life. ability = new SimpleActivatedAbility(new LoseLifeTargetEffect(3), new GenericManaCost(2)); diff --git a/Mage.Sets/src/mage/cards/e/EsperTerra.java b/Mage.Sets/src/mage/cards/e/EsperTerra.java new file mode 100644 index 00000000000..9397c73f147 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EsperTerra.java @@ -0,0 +1,125 @@ +package mage.cards.e; + +import mage.MageInt; +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.common.SagaAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenCopyTargetEffect; +import mage.abilities.effects.common.ExileAndReturnSourceEffect; +import mage.abilities.effects.mana.BasicManaEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.HasteAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledEnchantmentPermanent; +import mage.filter.predicate.Predicates; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EsperTerra extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledEnchantmentPermanent("nonlegendary enchantment you control"); + + static { + filter.add(Predicates.not(SuperType.LEGENDARY.getPredicate())); + } + + public EsperTerra(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, ""); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SAGA); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(6); + this.toughness = new MageInt(6); + this.nightCard = true; + this.color.setRed(true); + this.color.setGreen(true); + + // (As this Saga enters and after your draw step, add a lore counter.) + SagaAbility sagaAbility = new SagaAbility(this, SagaChapter.CHAPTER_IV); + + // I, II, III -- Create a token that's a copy of target nonlegendary enchantment you control. It gains haste. If it's a Saga, put up to three lore counters on it. Sacrifice it at the beginning of your next end step. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_I, SagaChapter.CHAPTER_III, + new EsperTerraEffect(), new TargetPermanent(filter) + ); + + // IV -- Add {W}{W}, {U}{U}, {B}{B}, {R}{R}, and {G}{G}. Exile Esper Terra, then return it to the battlefield. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_IV, + new BasicManaEffect(new Mana( + 2, 2, 2, 2, 2, 0, 0, 0 + )).setText("add {W}{W}, {U}{U}, {B}{B}, {R}{R}, and {G}{G}"), + new ExileAndReturnSourceEffect(PutCards.BATTLEFIELD)); + this.addAbility(sagaAbility); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + } + + private EsperTerra(final EsperTerra card) { + super(card); + } + + @Override + public EsperTerra copy() { + return new EsperTerra(this); + } +} + +class EsperTerraEffect extends OneShotEffect { + + EsperTerraEffect() { + super(Outcome.Benefit); + staticText = "create a token that's a copy of target nonlegendary enchantment you control. " + + "It gains haste. If it's a Saga, put up to three lore counters on it. " + + "Sacrifice it at the beginning of your next end step"; + } + + private EsperTerraEffect(final EsperTerraEffect effect) { + super(effect); + } + + @Override + public EsperTerraEffect copy() { + return new EsperTerraEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent == null) { + return false; + } + CreateTokenCopyTargetEffect effect = new CreateTokenCopyTargetEffect(); + effect.setSavedPermanent(permanent); + effect.addAdditionalAbilities(HasteAbility.getInstance()); + effect.apply(game, source); + for (Permanent token : effect.getAddedPermanents()) { + if (!token.hasSubtype(SubType.SAGA, game)) { + continue; + } + Optional.ofNullable(source.getControllerId()) + .map(game::getPlayer) + .map(player -> player.getAmount( + 0, 3, "Choose how many lore counters to put on " + token.getIdName(), source, game + )) + .filter(amount -> amount > 0) + .ifPresent(amount -> token.addCounters(CounterType.LORE.createInstance(amount), source, game)); + } + effect.sacrificeTokensCreatedAtNextEndStep(game, source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/e/EspersToMagicite.java b/Mage.Sets/src/mage/cards/e/EspersToMagicite.java new file mode 100644 index 00000000000..c65b8c06d8b --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EspersToMagicite.java @@ -0,0 +1,116 @@ +package mage.cards.e; + +import mage.abilities.Ability; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenCopyTargetEffect; +import mage.cards.*; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCardInExile; +import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EspersToMagicite extends CardImpl { + + public EspersToMagicite(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{B}"); + + // Exile each opponent's graveyard. When you do, choose up to one target creature card exiled this way. Create a token that's a copy of that card, except it's an artifact and it loses all other card types. + this.getSpellAbility().addEffect(new EspersToMagiciteExileEffect()); + } + + private EspersToMagicite(final EspersToMagicite card) { + super(card); + } + + @Override + public EspersToMagicite copy() { + return new EspersToMagicite(this); + } +} + +class EspersToMagiciteExileEffect extends OneShotEffect { + + EspersToMagiciteExileEffect() { + super(Outcome.Benefit); + staticText = "exile each opponent's graveyard. When you do, choose up to one target creature card exiled this way. " + + "Create a token that's a copy of that card, except it's an artifact and it loses all other card types"; + } + + private EspersToMagiciteExileEffect(final EspersToMagiciteExileEffect effect) { + super(effect); + } + + @Override + public EspersToMagiciteExileEffect copy() { + return new EspersToMagiciteExileEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Cards cards = new CardsImpl(); + for (UUID playerId : game.getOpponents(source.getControllerId())) { + Player opponent = game.getPlayer(playerId); + if (opponent != null) { + cards.addAll(opponent.getGraveyard()); + } + } + player.moveCardsToExile( + cards.getCards(game), source, game, true, + CardUtil.getExileZoneId(game, source), + CardUtil.getSourceName(game, source) + ); + ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility(new EspersToMagiciteTokenEffect(), false); + ability.addTarget(new TargetCardInExile( + 0, 1, + StaticFilters.FILTER_CARD_CREATURE, + CardUtil.getExileZoneId(game, source) + )); + game.fireReflexiveTriggeredAbility(ability, source); + return true; + } +} + +class EspersToMagiciteTokenEffect extends OneShotEffect { + + EspersToMagiciteTokenEffect() { + super(Outcome.Benefit); + staticText = "choose up to one target creature card exiled this way. " + + "Create a token that's a copy of that card, except it's an artifact and it loses all other card types"; + } + + private EspersToMagiciteTokenEffect(final EspersToMagiciteTokenEffect effect) { + super(effect); + } + + @Override + public EspersToMagiciteTokenEffect copy() { + return new EspersToMagiciteTokenEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Card card = game.getCard(getTargetPointer().getFirst(game, source)); + return card != null && + new CreateTokenCopyTargetEffect() + .setPermanentModifier(token -> { + token.removeAllCardTypes(); + token.addCardType(CardType.ARTIFACT); + }) + .setTargetPointer(new FixedTarget(card, game)) + .apply(game, source); + } +} diff --git a/Mage.Sets/src/mage/cards/e/EunuchsIntrigues.java b/Mage.Sets/src/mage/cards/e/EunuchsIntrigues.java index ff4e69d2012..439b5c416ff 100644 --- a/Mage.Sets/src/mage/cards/e/EunuchsIntrigues.java +++ b/Mage.Sets/src/mage/cards/e/EunuchsIntrigues.java @@ -67,10 +67,7 @@ class EunuchsIntriguesEffect extends OneShotEffect { FilterCreaturePermanent filter = new FilterCreaturePermanent("creature you control"); filter.add(new ControllerIdPredicate(player.getId())); Target target = new TargetPermanent(1, 1, filter, true); - if (target.canChoose(player.getId(), source, game)) { - while (!target.isChosen(game) && target.canChoose(player.getId(), source, game) && player.canRespond()) { - player.chooseTarget(Outcome.DestroyPermanent, target, source, game); - } + if (player.chooseTarget(Outcome.DestroyPermanent, target, source, game)) { Permanent permanent = game.getPermanent(target.getFirstTarget()); if (permanent != null) { game.informPlayers(player.getLogName() + " has chosen " + permanent.getLogName() + " as their only creature able to block this turn"); diff --git a/Mage.Sets/src/mage/cards/e/EvilReawakened.java b/Mage.Sets/src/mage/cards/e/EvilReawakened.java new file mode 100644 index 00000000000..745c306a11c --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EvilReawakened.java @@ -0,0 +1,34 @@ +package mage.cards.e; + +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldWithCounterTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EvilReawakened extends CardImpl { + + public EvilReawakened(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{B}"); + + // Return target creature card from your graveyard to the battlefield with two additional +1/+1 counters on it. + this.getSpellAbility().addEffect(new ReturnFromGraveyardToBattlefieldWithCounterTargetEffect(true, CounterType.P1P1.createInstance())); + this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_CREATURE_YOUR_GRAVEYARD)); + } + + private EvilReawakened(final EvilReawakened card) { + super(card); + } + + @Override + public EvilReawakened copy() { + return new EvilReawakened(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/Excavation.java b/Mage.Sets/src/mage/cards/e/Excavation.java index 9032a419272..fb2e79293c9 100644 --- a/Mage.Sets/src/mage/cards/e/Excavation.java +++ b/Mage.Sets/src/mage/cards/e/Excavation.java @@ -4,11 +4,11 @@ import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.SacrificeTargetCost; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.InfoEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.TargetController; -import mage.constants.Zone; import mage.filter.StaticFilters; import java.util.UUID; @@ -26,6 +26,7 @@ public final class Excavation extends CardImpl { SimpleActivatedAbility ability = new SimpleActivatedAbility(new DrawCardSourceControllerEffect(1), new ManaCostsImpl<>("{1}")); ability.addCost(new SacrificeTargetCost(StaticFilters.FILTER_LAND)); ability.setMayActivate(TargetController.ANY); + ability.addEffect(new InfoEffect("Any player may activate this ability")); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/e/ExpelTheInterlopers.java b/Mage.Sets/src/mage/cards/e/ExpelTheInterlopers.java index 562fd8d8fc2..6ca1c5a43f4 100644 --- a/Mage.Sets/src/mage/cards/e/ExpelTheInterlopers.java +++ b/Mage.Sets/src/mage/cards/e/ExpelTheInterlopers.java @@ -62,7 +62,7 @@ class ExpelTheInterlopersEffect extends OneShotEffect { } // Choose a number between 0 and 10. - int number = player.getAmount(0, 10, "Choose a number between 0 and 10", game); + int number = player.getAmount(0, 10, "Choose a number between 0 and 10", source, game); game.informPlayers(player.getLogName() + " has chosen the number " + number + "." + CardUtil.getSourceLogName(game, source)); // Destroy all creatures with power greater than or equal to the chosen number. diff --git a/Mage.Sets/src/mage/cards/e/ExpertLevelSafe.java b/Mage.Sets/src/mage/cards/e/ExpertLevelSafe.java index 629216547b3..37a2ff64722 100644 --- a/Mage.Sets/src/mage/cards/e/ExpertLevelSafe.java +++ b/Mage.Sets/src/mage/cards/e/ExpertLevelSafe.java @@ -72,8 +72,8 @@ class ExpertLevelSafeEffect extends OneShotEffect { return false; } - int controllerChoice = controller.getAmount(1, 3, "Choose a number", game); - int opponentChoice = opponent.getAmount(1, 3, "Choose a number", game); + int controllerChoice = controller.getAmount(1, 3, "Choose a number", source, game); + int opponentChoice = opponent.getAmount(1, 3, "Choose a number", source, game); game.informPlayers(controller.getLogName() + " chose " + controllerChoice); game.informPlayers(opponent.getLogName() + " chose " + opponentChoice); diff --git a/Mage.Sets/src/mage/cards/e/ExterminatorMagmarch.java b/Mage.Sets/src/mage/cards/e/ExterminatorMagmarch.java new file mode 100644 index 00000000000..6412c793bc8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/ExterminatorMagmarch.java @@ -0,0 +1,197 @@ +package mage.cards.e; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +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.filter.FilterPermanent; +import mage.filter.FilterSpell; +import mage.filter.StaticFilters; +import mage.filter.common.FilterInstantOrSorcerySpell; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.MageObjectReferencePredicate; +import mage.filter.predicate.other.HasOnlySingleTargetPermanentPredicate; +import mage.filter.predicate.permanent.ControllerIdPredicate; +import mage.game.Controllable; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.game.stack.Spell; +import mage.game.stack.StackObject; +import mage.players.Player; +import mage.target.Target; +import mage.target.TargetPermanent; +import mage.util.CardUtil; +import mage.util.functions.StackObjectCopyApplier; + +import java.util.*; + +/** + * @author TheElk801 + */ +public final class ExterminatorMagmarch extends CardImpl { + + public ExterminatorMagmarch(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{2}{B}{R}"); + + this.subtype.add(SubType.PHYREXIAN); + this.subtype.add(SubType.CONSTRUCT); + this.power = new MageInt(5); + this.toughness = new MageInt(3); + + // Whenever you cast an instant or sorcery spell that targets only a single nonland permanent an opponent controls, if another opponent controls one or more nonland permanents that spell could target, choose one of those permanents. Copy that spell. The copy targets the chosen permanent. + this.addAbility(new ExterminatorMagmarchTriggeredAbility()); + + // {1}{B}: Regenerate Exterminator Magmarch. + this.addAbility(new SimpleActivatedAbility(new RegenerateSourceEffect(), new ManaCostsImpl<>("{1}{B}"))); + } + + private ExterminatorMagmarch(final ExterminatorMagmarch card) { + super(card); + } + + @Override + public ExterminatorMagmarch copy() { + return new ExterminatorMagmarch(this); + } +} + +class ExterminatorMagmarchTriggeredAbility extends SpellCastControllerTriggeredAbility { + + private static final String OPPONENT_ID_KEY = "ExterminatorMagmarchOpponentKey"; + private static final FilterSpell filter = new FilterInstantOrSorcerySpell( + "an instant or sorcery spell that targets only a single nonland permanent an opponent controls, " + + "if another opponent controls one or more nonland permanents that spell could target" + ); + + static { + filter.add(new HasOnlySingleTargetPermanentPredicate(StaticFilters.FILTER_OPPONENTS_PERMANENT_NON_LAND)); + } + + ExterminatorMagmarchTriggeredAbility() { + super(new ExterminatorMagmarchEffect(), filter, false); + } + + private ExterminatorMagmarchTriggeredAbility(final ExterminatorMagmarchTriggeredAbility ability) { + super(ability); + } + + @Override + public ExterminatorMagmarchTriggeredAbility copy() { + return new ExterminatorMagmarchTriggeredAbility(this); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (!super.checkTrigger(event, game)) { + return false; + } + this.getAllEffects().setValue( + OPPONENT_ID_KEY, + game.getSpell(event.getTargetId()) + .getSpellAbility() + .getModes() + .values() + .stream() + .map(Mode::getTargets) + .flatMap(Collection::stream) + .map(Target::getTargets) + .flatMap(Collection::stream) + .map(game::getPermanent) + .filter(Objects::nonNull) + .map(Controllable::getControllerId) + .filter(Objects::nonNull) + .findFirst() + .orElse(null) + ); + return true; + } + + @Override + public boolean checkInterveningIfClause(Game game) { + return CardUtil + .getEffectValueFromAbility(this, OPPONENT_ID_KEY, UUID.class) + .map(opponentId -> { + FilterPermanent filter = StaticFilters.FILTER_OPPONENTS_PERMANENT_NON_LAND.copy(); + filter.add(Predicates.not(new ControllerIdPredicate(opponentId))); + return game.getBattlefield().contains(filter, this, game, 1); + }) + .orElse(false); + } + + public static String getKey() { + return OPPONENT_ID_KEY; + } +} + +class ExterminatorMagmarchEffect extends OneShotEffect { + + private static class ExterminatorMagmarchApplier implements StackObjectCopyApplier { + + private final Iterator predicate; + + ExterminatorMagmarchApplier(Permanent permanent, Game game) { + this.predicate = Arrays.asList(new MageObjectReferencePredicate(permanent, game)).iterator(); + } + + @Override + public void modifySpell(StackObject stackObject, Game game) { + } + + @Override + public MageObjectReferencePredicate getNextNewTargetType() { + if (predicate.hasNext()) { + return predicate.next(); + } + return null; + } + } + + ExterminatorMagmarchEffect() { + super(Outcome.Benefit); + staticText = "choose one of those permanents. Copy that spell. The copy targets the chosen permanent"; + } + + private ExterminatorMagmarchEffect(final ExterminatorMagmarchEffect effect) { + super(effect); + } + + @Override + public ExterminatorMagmarchEffect copy() { + return new ExterminatorMagmarchEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Spell spell = (Spell) getValue("spellCast"); + UUID opponentId = (UUID) getValue(ExterminatorMagmarchTriggeredAbility.getKey()); + if (player == null || spell == null || opponentId == null) { + return false; + } + FilterPermanent filter = StaticFilters.FILTER_OPPONENTS_PERMANENT_NON_LAND.copy(); + filter.add(Predicates.not(new ControllerIdPredicate(opponentId))); + TargetPermanent target = new TargetPermanent(filter); + target.withChooseHint("to target with the copy"); + target.withNotTarget(true); + player.choose(outcome, target, source, game); + Permanent permanent = game.getPermanent(target.getFirstTarget()); + if (permanent == null) { + return false; + } + spell.createCopyOnStack( + game, source, source.getControllerId(), false, + 1, new ExterminatorMagmarchApplier(permanent, game) + ); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/e/EyeOfNidhogg.java b/Mage.Sets/src/mage/cards/e/EyeOfNidhogg.java new file mode 100644 index 00000000000..cff808d64d4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EyeOfNidhogg.java @@ -0,0 +1,74 @@ +package mage.cards.e; + +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.abilities.common.PutIntoGraveFromBattlefieldSourceTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.ReturnToHandSourceEffect; +import mage.abilities.effects.common.combat.GoadAttachedEffect; +import mage.abilities.effects.common.continuous.AddCardSubtypeAttachedEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.effects.common.continuous.SetBasePowerToughnessAttachedEffect; +import mage.abilities.effects.common.continuous.SetCardColorAttachedEffect; +import mage.abilities.keyword.DeathtouchAbility; +import mage.abilities.keyword.EnchantAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EyeOfNidhogg extends CardImpl { + + public EyeOfNidhogg(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.AURA); + + // Enchant creature + TargetPermanent auraTarget = new TargetCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + this.addAbility(new EnchantAbility(auraTarget)); + + // Enchanted creature is a black Dragon with base power and toughness 4/2, has flying and deathtouch, and is goaded. + Ability ability = new SimpleStaticAbility(new SetCardColorAttachedEffect( + ObjectColor.BLACK, Duration.WhileControlled, AttachmentType.AURA + ).setText("enchanted creature is a black")); + ability.addEffect(new AddCardSubtypeAttachedEffect( + SubType.DRAGON, AttachmentType.AURA + ).setText("Dragon with power")); + ability.addEffect(new SetBasePowerToughnessAttachedEffect( + 4, 2, + AttachmentType.AURA).setText("and toughness 4/2")); + ability.addEffect(new GainAbilityAttachedEffect( + FlyingAbility.getInstance(), AttachmentType.AURA + ).setText(", has flying")); + ability.addEffect(new GainAbilityAttachedEffect( + DeathtouchAbility.getInstance(), AttachmentType.AURA + ).setText("and deathtouch")); + ability.addEffect(new GoadAttachedEffect().concatBy(",")); + this.addAbility(ability); + + // When Eye of Nidhogg is put into a graveyard from the battlefield, return it to its owner's hand. + this.addAbility(new PutIntoGraveFromBattlefieldSourceTriggeredAbility(new ReturnToHandSourceEffect() + .setText("return it to its owner's hand"))); + } + + private EyeOfNidhogg(final EyeOfNidhogg card) { + super(card); + } + + @Override + public EyeOfNidhogg copy() { + return new EyeOfNidhogg(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/EyeOfSingularity.java b/Mage.Sets/src/mage/cards/e/EyeOfSingularity.java index 57fa01e7785..ffdfc183b1f 100644 --- a/Mage.Sets/src/mage/cards/e/EyeOfSingularity.java +++ b/Mage.Sets/src/mage/cards/e/EyeOfSingularity.java @@ -127,7 +127,7 @@ class EyeOfSingularityTriggeredAbility extends TriggeredAbilityImpl { } if (permanent != null && !permanent.isBasic(game)) { - getEffects().get(0).setTargetPointer(new FixedTarget(event.getTargetId())); + getEffects().get(0).setTargetPointer(new FixedTarget(permanent, game)); return true; } return false; diff --git a/Mage.Sets/src/mage/cards/f/FadeAway.java b/Mage.Sets/src/mage/cards/f/FadeAway.java index c8fe2971b65..0299aae8f1e 100644 --- a/Mage.Sets/src/mage/cards/f/FadeAway.java +++ b/Mage.Sets/src/mage/cards/f/FadeAway.java @@ -64,7 +64,7 @@ class FadeAwayEffect extends OneShotEffect { int payAmount = 0; boolean paid = false; while (player.canRespond() && !paid) { - payAmount = player.getAmount(0, creaturesNumber, message, game); + payAmount = player.getAmount(0, creaturesNumber, message, source, game); ManaCostsImpl cost = new ManaCostsImpl<>(); cost.add(new GenericManaCost(payAmount)); cost.clearPaid(); diff --git a/Mage.Sets/src/mage/cards/f/FaithHealer.java b/Mage.Sets/src/mage/cards/f/FaithHealer.java index 2820c02bf85..fe218db50b6 100644 --- a/Mage.Sets/src/mage/cards/f/FaithHealer.java +++ b/Mage.Sets/src/mage/cards/f/FaithHealer.java @@ -1,4 +1,3 @@ - package mage.cards.f; import mage.MageInt; @@ -10,10 +9,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; import mage.filter.StaticFilters; -import mage.filter.common.FilterControlledEnchantmentPermanent; -import mage.target.common.TargetControlledPermanent; import java.util.UUID; @@ -29,8 +25,9 @@ public final class FaithHealer extends CardImpl { this.power = new MageInt(1); this.toughness = new MageInt(1); - // Sacrifice an enchantment: You gain life equal to the sacrificed enchantment's converted mana cost. - this.addAbility(new SimpleActivatedAbility(new GainLifeEffect(SacrificeCostManaValue.ENCHANTMENT), + // Sacrifice an enchantment: You gain life equal to the sacrificed enchantment's mana value. + this.addAbility(new SimpleActivatedAbility(new GainLifeEffect(SacrificeCostManaValue.ENCHANTMENT) + .setText("you gain life equal to the sacrificed enchantment's mana value"), new SacrificeTargetCost(StaticFilters.FILTER_PERMANENT_ENCHANTMENT))); } diff --git a/Mage.Sets/src/mage/cards/f/FaithsShield.java b/Mage.Sets/src/mage/cards/f/FaithsShield.java index 86450780d32..fa78a4a0f78 100644 --- a/Mage.Sets/src/mage/cards/f/FaithsShield.java +++ b/Mage.Sets/src/mage/cards/f/FaithsShield.java @@ -1,7 +1,6 @@ package mage.cards.f; -import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.condition.common.FatefulHourCondition; @@ -17,13 +16,15 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Outcome; import mage.filter.FilterCard; +import mage.filter.StaticFilters; import mage.filter.predicate.mageobject.ColorPredicate; import mage.game.Game; import mage.players.Player; import mage.target.common.TargetControlledPermanent; +import java.util.UUID; + /** - * * @author BetaSteward */ public final class FaithsShield extends CardImpl { @@ -63,29 +64,25 @@ class FaithsShieldEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); MageObject mageObject = game.getObject(source); - if (controller != null && mageObject != null) { - if (FatefulHourCondition.instance.apply(game, source)) { - ChoiceColor choice = new ChoiceColor(); - if (!controller.choose(Outcome.Protect, choice, game)) { - return false; - } - if (choice.getColor() != null) { - game.informPlayers(mageObject.getLogName() + ": " + controller.getLogName() + " has chosen " + choice.getChoice()); - FilterCard filter = new FilterCard(); - filter.add(new ColorPredicate(choice.getColor())); - filter.setMessage(choice.getChoice()); - Ability ability = new ProtectionAbility(filter); - game.addEffect(new GainAbilityControlledEffect(ability, Duration.EndOfTurn), source); - game.addEffect(new GainAbilityControllerEffect(ability, Duration.EndOfTurn), source); - return true; - } - } else { - game.addEffect(new GainProtectionFromColorTargetEffect(Duration.EndOfTurn), source); - return true; - } - + if (controller == null || mageObject == null) { + return false; } - return false; + if (!FatefulHourCondition.instance.apply(game, source)) { + game.addEffect(new GainProtectionFromColorTargetEffect(Duration.EndOfTurn), source); + return true; + } + ChoiceColor choice = new ChoiceColor(); + if (!controller.choose(Outcome.Protect, choice, game) || choice.getColor() == null) { + return false; + } + game.informPlayers(mageObject.getLogName() + ": " + controller.getLogName() + " has chosen " + choice.getChoice()); + FilterCard filter = new FilterCard(); + filter.add(new ColorPredicate(choice.getColor())); + filter.setMessage(choice.getChoice()); + Ability ability = new ProtectionAbility(filter); + game.addEffect(new GainAbilityControlledEffect(ability, Duration.EndOfTurn, StaticFilters.FILTER_PERMANENT), source); + game.addEffect(new GainAbilityControllerEffect(ability, Duration.EndOfTurn), source); + return true; } @Override diff --git a/Mage.Sets/src/mage/cards/f/FallOfTheFirstCivilization.java b/Mage.Sets/src/mage/cards/f/FallOfTheFirstCivilization.java new file mode 100644 index 00000000000..ca51988dc54 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FallOfTheFirstCivilization.java @@ -0,0 +1,145 @@ +package mage.cards.f; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.SagaAbility; +import mage.abilities.effects.Effects; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.DrawCardTargetEffect; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SagaChapter; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetOpponent; +import mage.util.CardUtil; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * @author TheElk801 + */ +public final class FallOfTheFirstCivilization extends CardImpl { + + public FallOfTheFirstCivilization(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{W}"); + + this.subtype.add(SubType.SAGA); + + // (As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.) + SagaAbility sagaAbility = new SagaAbility(this); + + // I -- You and target opponent each draw two cards. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_I, + new Effects( + new DrawCardSourceControllerEffect(1).setText("you"), + new DrawCardTargetEffect(2).setText("and target opponent each draw two cards") + ), new TargetOpponent() + ); + + // II -- Exile target artifact an opponent controls. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_II, new ExileTargetEffect(), + new TargetPermanent(StaticFilters.FILTER_OPPONENTS_PERMANENT_ARTIFACT) + ); + + // III -- Each player chooses three nonland permanents they control. Destroy all other nonland permanents. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_III, new FallOfTheFirstCivilizationEffect()); + this.addAbility(sagaAbility); + } + + private FallOfTheFirstCivilization(final FallOfTheFirstCivilization card) { + super(card); + } + + @Override + public FallOfTheFirstCivilization copy() { + return new FallOfTheFirstCivilization(this); + } +} + +class FallOfTheFirstCivilizationEffect extends OneShotEffect { + + FallOfTheFirstCivilizationEffect() { + super(Outcome.Benefit); + staticText = "each player chooses three nonland permanents they control. Destroy all other nonland permanents"; + } + + private FallOfTheFirstCivilizationEffect(final FallOfTheFirstCivilizationEffect effect) { + super(effect); + } + + @Override + public FallOfTheFirstCivilizationEffect copy() { + return new FallOfTheFirstCivilizationEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Set permanents = new HashSet<>(); + for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { + Player player = game.getPlayer(playerId); + if (player == null) { + continue; + } + int count = game + .getBattlefield() + .count(StaticFilters.FILTER_CONTROLLED_PERMANENT_NON_LAND, playerId, source, game); + Set toSave = new HashSet<>(); + switch (count) { + case 0: + break; + case 1: + case 2: + case 3: + toSave.addAll(game + .getBattlefield() + .getActivePermanents( + StaticFilters.FILTER_CONTROLLED_PERMANENT_NON_LAND, + playerId, source, game + )); + break; + default: + TargetPermanent target = new TargetControlledCreaturePermanent(3); + target.withNotTarget(true); + target.withChooseHint("to prevent being destroyed"); + player.choose(outcome, target, source, game); + target.getTargets() + .stream() + .map(game::getPermanent) + .filter(Objects::nonNull) + .forEach(toSave::add); + } + if (toSave.isEmpty()) { + game.informPlayers(player.getLogName() + " chooses no permanents"); + continue; + } + game.informPlayers(player.getLogName() + " chooses " + + CardUtil.concatWithAnd(toSave.stream().map(MageObject::getLogName).collect(Collectors.toList()))); + permanents.addAll(toSave); + } + for (Permanent permanent : game.getBattlefield().getActivePermanents( + StaticFilters.FILTER_PERMANENT_NON_LAND, + source.getControllerId(), source, game + )) { + if (!permanents.contains(permanent)) { + permanent.destroy(source, game); + } + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/f/FangFearlessLCie.java b/Mage.Sets/src/mage/cards/f/FangFearlessLCie.java new file mode 100644 index 00000000000..cf71fd538a4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FangFearlessLCie.java @@ -0,0 +1,54 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.CardsLeaveGraveyardTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.InfoEffect; +import mage.abilities.effects.common.LoseLifeSourceControllerEffect; +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 java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FangFearlessLCie extends CardImpl { + + public FangFearlessLCie(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + this.meldsWithClazz = mage.cards.v.VanilleCheerfulLCie.class; + this.meldsToClazz = mage.cards.r.RagnarokDivineDeliverance.class; + + // Whenever one or more cards leave your graveyard, you draw a card and you lose 1 life. This ability triggers only once each turn. + Ability ability = new CardsLeaveGraveyardTriggeredAbility(new DrawCardSourceControllerEffect(1, true)); + ability.addEffect(new LoseLifeSourceControllerEffect(1).concatBy("and")); + this.addAbility(ability); + + // (Melds with Vanille, Cheerful l'Cie.) + this.addAbility(new SimpleStaticAbility( + Zone.ALL, new InfoEffect("(Melds with Vanille, Cheerful l'Cie.)") + )); + } + + private FangFearlessLCie(final FangFearlessLCie card) { + super(card); + } + + @Override + public FangFearlessLCie copy() { + return new FangFearlessLCie(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FatalLore.java b/Mage.Sets/src/mage/cards/f/FatalLore.java index b0489701406..73de55ba270 100644 --- a/Mage.Sets/src/mage/cards/f/FatalLore.java +++ b/Mage.Sets/src/mage/cards/f/FatalLore.java @@ -98,7 +98,7 @@ class FatalLoreEffect extends OneShotEffect { if (player == null) { return false; } - int toDraw = player.getAmount(0, 3, "Choose how many cards to draw", game); + int toDraw = player.getAmount(0, 3, "Choose how many cards to draw", source, game); return player.drawCards(toDraw, source, game) > 0; } } diff --git a/Mage.Sets/src/mage/cards/f/FatalPush.java b/Mage.Sets/src/mage/cards/f/FatalPush.java index 19452081f46..cf50d901f9b 100644 --- a/Mage.Sets/src/mage/cards/f/FatalPush.java +++ b/Mage.Sets/src/mage/cards/f/FatalPush.java @@ -1,22 +1,21 @@ - package mage.cards.f; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.condition.common.RevoltCondition; import mage.abilities.effects.OneShotEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.constants.AbilityWord; import mage.constants.CardType; import mage.constants.Outcome; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.players.Player; import mage.target.common.TargetCreaturePermanent; import mage.watchers.common.RevoltWatcher; +import java.util.UUID; + /** - * * @author emerald000 */ public final class FatalPush extends CardImpl { @@ -46,7 +45,9 @@ class FatalPushEffect extends OneShotEffect { FatalPushEffect() { super(Outcome.DestroyPermanent); - this.staticText = "Destroy target creature if it has mana value 2 or less.
Revolt — Destroy that creature if it has mana value 4 or less instead if a permanent you controlled left the battlefield this turn"; + this.staticText = "Destroy target creature if it has mana value 2 or less.
" + + AbilityWord.REVOLT.formatWord() + "Destroy that creature if it has mana value 4 " + + "or less instead if a permanent you controlled left the battlefield this turn"; } private FatalPushEffect(final FatalPushEffect effect) { @@ -60,16 +61,13 @@ class FatalPushEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - Permanent targetCreature = game.getPermanent(this.getTargetPointer().getFirst(game, source)); - if (targetCreature != null) { - int cmc = targetCreature.getManaValue(); - if (cmc <= 2 - || (RevoltCondition.instance.apply(game, source) && cmc <= 4)) { - targetCreature.destroy(source, game, false); - } - } + Permanent targetCreature = game.getPermanent(this.getTargetPointer().getFirst(game, source)); + if (targetCreature == null) { + return false; + } + int cmc = targetCreature.getManaValue(); + if ((cmc <= 2 || (RevoltCondition.instance.apply(game, source) && cmc <= 4)) + && targetCreature.destroy(source, game, false)) { return true; } return false; diff --git a/Mage.Sets/src/mage/cards/f/FateOfTheSunCryst.java b/Mage.Sets/src/mage/cards/f/FateOfTheSunCryst.java new file mode 100644 index 00000000000..e0e0bf34626 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FateOfTheSunCryst.java @@ -0,0 +1,54 @@ +package mage.cards.f; + +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.SourceTargetsPermanentCondition; +import mage.abilities.effects.common.DestroyTargetEffect; +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.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.TappedPredicate; +import mage.target.common.TargetNonlandPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FateOfTheSunCryst extends CardImpl { + + private static final FilterPermanent filter + = new FilterCreaturePermanent("a tapped creature"); + + static { + filter.add(TappedPredicate.TAPPED); + } + + private static final Condition condition = new SourceTargetsPermanentCondition(filter); + + public FateOfTheSunCryst(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{4}{W}"); + + // This spell costs {2} less to cast if it targets a tapped creature. + this.addAbility(new SimpleStaticAbility( + Zone.ALL, new SpellCostReductionSourceEffect(2, condition).setCanWorksOnStackOnly(true) + ).setRuleAtTheTop(true)); + + // Destroy target nonland permanent. + this.getSpellAbility().addEffect(new DestroyTargetEffect()); + this.getSpellAbility().addTarget(new TargetNonlandPermanent()); + } + + private FateOfTheSunCryst(final FateOfTheSunCryst card) { + super(card); + } + + @Override + public FateOfTheSunCryst copy() { + return new FateOfTheSunCryst(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FireMagic.java b/Mage.Sets/src/mage/cards/f/FireMagic.java new file mode 100644 index 00000000000..91bb0da53b4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FireMagic.java @@ -0,0 +1,47 @@ +package mage.cards.f; + +import mage.abilities.Mode; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.DamageAllEffect; +import mage.abilities.keyword.TieredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FireMagic extends CardImpl { + + public FireMagic(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{R}"); + + // Tiered + this.addAbility(new TieredAbility(this)); + + // * Fire -- {0} -- Fire Magic deals 1 damage to each creature. + this.getSpellAbility().addEffect(new DamageAllEffect(1, StaticFilters.FILTER_PERMANENT_CREATURE)); + this.getSpellAbility().withFirstModeCost(new GenericManaCost(1)); + this.getSpellAbility().withFirstModeFlavorWord("Fire"); + + // * Fira -- {2} -- Fire Magic deals 2 damage to each creature. + this.getSpellAbility().addMode(new Mode(new DamageAllEffect(2, StaticFilters.FILTER_PERMANENT_CREATURE)) + .withCost(new GenericManaCost(2)).withFlavorWord("Fira")); + + // * Firaga -- {5} -- Fire Magic deals 3 damage to each creature. + this.getSpellAbility().addMode(new Mode(new DamageAllEffect(3, StaticFilters.FILTER_PERMANENT_CREATURE)) + .withCost(new GenericManaCost(5)).withFlavorWord("Firaga")); + } + + private FireMagic(final FireMagic card) { + super(card); + } + + @Override + public FireMagic copy() { + return new FireMagic(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FirionWildRoseWarrior.java b/Mage.Sets/src/mage/cards/f/FirionWildRoseWarrior.java new file mode 100644 index 00000000000..b70c409f3da --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FirionWildRoseWarrior.java @@ -0,0 +1,131 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenCopyTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.effects.common.cost.CostModificationEffectImpl; +import mage.abilities.keyword.EquipAbility; +import mage.abilities.keyword.HasteAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.EquippedPredicate; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FirionWildRoseWarrior extends CardImpl { + + private static final FilterPermanent filter + = new FilterCreaturePermanent("equipped creatures"); + private static final FilterPermanent filter2 + = new FilterControlledPermanent(SubType.EQUIPMENT, "a nontoken Equipment you control"); + + static { + filter.add(EquippedPredicate.instance); + filter2.add(TokenPredicate.FALSE); + } + + public FirionWildRoseWarrior(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.REBEL); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Equipped creatures you control have haste. + this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect( + HasteAbility.getInstance(), Duration.WhileOnBattlefield, filter + ))); + + // Whenever a nontoken Equipment you control enters, create a token that's a copy of it, except it has "This Equipment's equip abilities cost {2} less to activate." Sacrifice that token at the beginning of the next upkeep. + this.addAbility(new EntersBattlefieldAllTriggeredAbility(new FirionWildRoseWarriorEffect(), filter2)); + } + + private FirionWildRoseWarrior(final FirionWildRoseWarrior card) { + super(card); + } + + @Override + public FirionWildRoseWarrior copy() { + return new FirionWildRoseWarrior(this); + } +} + +class FirionWildRoseWarriorEffect extends OneShotEffect { + + FirionWildRoseWarriorEffect() { + super(Outcome.Benefit); + staticText = "create a token that's a copy of it, except it has " + + "\"This Equipment's equip abilities cost {2} less to activate.\" " + + "Sacrifice that token at the beginning of the next upkeep"; + } + + private FirionWildRoseWarriorEffect(final FirionWildRoseWarriorEffect effect) { + super(effect); + } + + @Override + public FirionWildRoseWarriorEffect copy() { + return new FirionWildRoseWarriorEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = (Permanent) getValue("permanentEnteringBattlefield"); + if (permanent == null) { + return false; + } + CreateTokenCopyTargetEffect effect = new CreateTokenCopyTargetEffect(); + effect.addAdditionalAbilities(new SimpleStaticAbility(new FirionWildRoseWarriorReductionEffect())); + effect.setSavedPermanent(permanent); + effect.apply(game, source); + effect.sacrificeTokensCreatedAtNextEndStep(game, source); + return true; + } +} + +class FirionWildRoseWarriorReductionEffect extends CostModificationEffectImpl { + + FirionWildRoseWarriorReductionEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit, CostModificationType.REDUCE_COST); + staticText = "this Equipment's equip abilities cost {2} less to activate"; + } + + private FirionWildRoseWarriorReductionEffect(final FirionWildRoseWarriorReductionEffect effect) { + super(effect); + } + + @Override + public FirionWildRoseWarriorReductionEffect copy() { + return new FirionWildRoseWarriorReductionEffect(this); + } + + @Override + public boolean apply(Game game, Ability source, Ability abilityToModify) { + CardUtil.reduceCost(abilityToModify, 2); + return true; + } + + @Override + public boolean applies(Ability abilityToModify, Ability source, Game game) { + return abilityToModify instanceof EquipAbility + && source.getSourceId().equals(abilityToModify.getSourceId()); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FlameFusillade.java b/Mage.Sets/src/mage/cards/f/FlameFusillade.java index 1581f9b7e16..140d008fe84 100644 --- a/Mage.Sets/src/mage/cards/f/FlameFusillade.java +++ b/Mage.Sets/src/mage/cards/f/FlameFusillade.java @@ -1,6 +1,5 @@ package mage.cards.f; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; @@ -10,22 +9,23 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; -import mage.constants.Zone; +import mage.filter.StaticFilters; import mage.target.common.TargetAnyTarget; +import java.util.UUID; + /** - * * @author Loki */ public final class FlameFusillade extends CardImpl { public FlameFusillade(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{3}{R}"); + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{R}"); // Until end of turn, permanents you control gain "{tap}: This permanent deals 1 damage to any target." Ability gainedAbility = new SimpleActivatedAbility(new DamageTargetEffect(1, "this permanent"), new TapSourceCost()); gainedAbility.addTarget(new TargetAnyTarget()); - this.getSpellAbility().addEffect(new GainAbilityControlledEffect(gainedAbility, Duration.EndOfTurn).withDurationRuleAtStart(true)); + this.getSpellAbility().addEffect(new GainAbilityControlledEffect(gainedAbility, Duration.EndOfTurn, StaticFilters.FILTER_PERMANENTS).withDurationRuleAtStart(true)); } private FlameFusillade(final FlameFusillade card) { diff --git a/Mage.Sets/src/mage/cards/f/FlameblastDragon.java b/Mage.Sets/src/mage/cards/f/FlameblastDragon.java index 805dcde3cb1..1b889e3a381 100644 --- a/Mage.Sets/src/mage/cards/f/FlameblastDragon.java +++ b/Mage.Sets/src/mage/cards/f/FlameblastDragon.java @@ -68,7 +68,7 @@ class FlameblastDragonEffect extends OneShotEffect { ManaCosts cost = new ManaCostsImpl<>("{X}{R}"); if (player != null) { if (player.chooseUse(Outcome.Damage, "Pay " + cost.getText() + "? If you do, Flameblast Dragon deals X damage to any target", source, game)) { - int costX = player.announceXMana(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source); + int costX = player.announceX(0, Integer.MAX_VALUE, "Announce the value for {X} (pay to damage)", game, source, true); cost.add(new GenericManaCost(costX)); if (cost.pay(source, game, source, source.getControllerId(), false, null)) { Permanent permanent = game.getPermanent(source.getFirstTarget()); diff --git a/Mage.Sets/src/mage/cards/f/FlamewakePhoenix.java b/Mage.Sets/src/mage/cards/f/FlamewakePhoenix.java index 9eb548f88ac..e5f4600d788 100644 --- a/Mage.Sets/src/mage/cards/f/FlamewakePhoenix.java +++ b/Mage.Sets/src/mage/cards/f/FlamewakePhoenix.java @@ -2,21 +2,17 @@ package mage.cards.f; import mage.MageInt; import mage.abilities.common.AttacksEachCombatStaticAbility; -import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; import mage.abilities.condition.common.FerociousCondition; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.common.DoIfCostPaid; -import mage.abilities.effects.common.ReturnToBattlefieldUnderOwnerControlSourceEffect; +import mage.abilities.effects.common.ReturnSourceFromGraveyardToBattlefieldEffect; import mage.abilities.hint.common.FerociousHint; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.HasteAbility; +import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.TargetController; -import mage.constants.Zone; +import mage.constants.*; import java.util.UUID; @@ -33,20 +29,19 @@ public final class FlamewakePhoenix extends CardImpl { // Flying this.addAbility(FlyingAbility.getInstance()); + // Haste this.addAbility(HasteAbility.getInstance()); + // Flamewake Phoenix attacks each turn if able. this.addAbility(new AttacksEachCombatStaticAbility()); // Ferocious — At the beginning of combat on your turn, if you control a creature with power 4 or greater, you may pay {R}. If you do, return Flamewake Phoenix from your graveyard to the battlefield. - this.addAbility(new ConditionalInterveningIfTriggeredAbility( - new BeginningOfCombatTriggeredAbility( - Zone.GRAVEYARD, - TargetController.YOU, new DoIfCostPaid(new ReturnToBattlefieldUnderOwnerControlSourceEffect(), new ManaCostsImpl<>("{R}")), - false), - FerociousCondition.instance, - "Ferocious — At the beginning of combat on your turn, if you control a creature with power 4 or greater, you may pay {R}. If you do, return {this} from your graveyard to the battlefield." - ).addHint(FerociousHint.instance)); + this.addAbility(new BeginningOfCombatTriggeredAbility( + Zone.GRAVEYARD, TargetController.YOU, + new DoIfCostPaid(new ReturnSourceFromGraveyardToBattlefieldEffect(), new ManaCostsImpl<>("{R}")), + false + ).withInterveningIf(FerociousCondition.instance).setAbilityWord(AbilityWord.FEROCIOUS).addHint(FerociousHint.instance)); } private FlamewakePhoenix(final FlamewakePhoenix card) { diff --git a/Mage.Sets/src/mage/cards/f/FloralEvoker.java b/Mage.Sets/src/mage/cards/f/FloralEvoker.java index 29a9d87389b..5e104f4f6ee 100644 --- a/Mage.Sets/src/mage/cards/f/FloralEvoker.java +++ b/Mage.Sets/src/mage/cards/f/FloralEvoker.java @@ -40,7 +40,7 @@ public final class FloralEvoker extends CardImpl { new ReturnFromGraveyardToBattlefieldTargetEffect(true), new ManaCostsImpl<>("{G}") ); ability.addCost(new DiscardTargetCost(new TargetCardInHand(StaticFilters.FILTER_CARD_CREATURE_A))); - ability.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_LAND)); + ability.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_LAND_FROM_YOUR_GRAVEYARD)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/f/Flux.java b/Mage.Sets/src/mage/cards/f/Flux.java index 018843af766..6b58a0ae6f4 100644 --- a/Mage.Sets/src/mage/cards/f/Flux.java +++ b/Mage.Sets/src/mage/cards/f/Flux.java @@ -59,7 +59,7 @@ class FluxEffect extends OneShotEffect { for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { Player player = game.getPlayer(playerId); if (player != null) { - int numToDiscard = player.getAmount(0, player.getHand().size(), "Discard how many cards?", game); + int numToDiscard = player.getAmount(0, player.getHand().size(), "Discard how many cards?", source, game); player.discard(numToDiscard, false, false, source, game); player.drawCards(numToDiscard, source, game); } diff --git a/Mage.Sets/src/mage/cards/f/ForgottenAncient.java b/Mage.Sets/src/mage/cards/f/ForgottenAncient.java index e17e8b49ea1..748b0db0823 100644 --- a/Mage.Sets/src/mage/cards/f/ForgottenAncient.java +++ b/Mage.Sets/src/mage/cards/f/ForgottenAncient.java @@ -107,7 +107,7 @@ class ForgottenAncientEffect extends OneShotEffect { break; } - int amountToMove = controller.getAmount(0, numCounters, "Choose how many counters to move (" + numCounters + " counters remaining.)", game); + int amountToMove = controller.getAmount(0, numCounters, "Choose how many counters to move (" + numCounters + " counters remaining.)", source, game); if (amountToMove == 0) { break; } diff --git a/Mage.Sets/src/mage/cards/f/FrenziedGorespawn.java b/Mage.Sets/src/mage/cards/f/FrenziedGorespawn.java new file mode 100644 index 00000000000..e18d8b20a1a --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FrenziedGorespawn.java @@ -0,0 +1,87 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.combat.GoadTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.DefenderAttackedEvent; +import mage.game.events.GameEvent; +import mage.target.common.TargetCreaturePermanent; +import mage.target.targetadjustment.ForEachOpponentTargetsAdjuster; +import mage.target.targetpointer.FixedTargets; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FrenziedGorespawn extends CardImpl { + + public FrenziedGorespawn(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}{R}"); + + this.subtype.add(SubType.HORROR); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // When Frenzied Gorespawn enters the battlefield, for each opponent, goad target creature that player controls. + Ability ability = new EntersBattlefieldTriggeredAbility(new GoadTargetEffect() + .setText("for each opponent, goad target creature that player controls")); + ability.addTarget(new TargetCreaturePermanent()); + ability.setTargetAdjuster(new ForEachOpponentTargetsAdjuster()); + this.addAbility(ability); + + // Whenever one or more creatures attack one of your opponents, those creatures gain menace until end of turn. + this.addAbility(new FrenziedGorespawnTriggeredAbility()); + } + + private FrenziedGorespawn(final FrenziedGorespawn card) { + super(card); + } + + @Override + public FrenziedGorespawn copy() { + return new FrenziedGorespawn(this); + } +} + +class FrenziedGorespawnTriggeredAbility extends TriggeredAbilityImpl { + + FrenziedGorespawnTriggeredAbility() { + super(Zone.BATTLEFIELD, new GainAbilityTargetEffect(new MenaceAbility(false)) + .setText("those creatures gain menace until end of turn")); + this.setTriggerPhrase("Whenever one or more creatures attack one of your opponents, "); + } + + private FrenziedGorespawnTriggeredAbility(final FrenziedGorespawnTriggeredAbility ability) { + super(ability); + } + + @Override + public FrenziedGorespawnTriggeredAbility copy() { + return new FrenziedGorespawnTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DEFENDER_ATTACKED; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (!game.getOpponents(this.getControllerId()).contains(event.getTargetId())) { + return false; + } + this.getEffects().setTargetPointer(new FixedTargets(((DefenderAttackedEvent) event).getAttackers(game), game)); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/f/FreyaCrescent.java b/Mage.Sets/src/mage/cards/f/FreyaCrescent.java new file mode 100644 index 00000000000..25ca237a186 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FreyaCrescent.java @@ -0,0 +1,123 @@ +package mage.cards.f; + +import mage.ConditionalMana; +import mage.MageInt; +import mage.MageObject; +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.SpellAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.MyTurnCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.keyword.EquipAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.mana.ConditionalColoredManaAbility; +import mage.abilities.mana.builder.ConditionalManaBuilder; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterSpell; +import mage.game.Game; +import mage.game.command.Commander; +import mage.game.stack.Spell; +import mage.game.stack.StackObject; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class FreyaCrescent extends CardImpl { + + public FreyaCrescent(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.RAT); + this.subtype.add(SubType.KNIGHT); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Jump -- During your turn, Freya Crescent has flying. + this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( + new GainAbilitySourceEffect(FlyingAbility.getInstance()), + MyTurnCondition.instance, "during your turn, {this} has flying" + )).withFlavorWord("Jump")); + + // {T}: Add {R}. Spend this mana only to cast an Equipment spell or activate an equip ability. + this.addAbility(new ConditionalColoredManaAbility(Mana.RedMana(1), new FreyaCrescentManaBuilder())); + } + + private FreyaCrescent(final FreyaCrescent card) { + super(card); + } + + @Override + public FreyaCrescent copy() { + return new FreyaCrescent(this); + } +} + +class FreyaCrescentManaBuilder extends ConditionalManaBuilder { + + @Override + public ConditionalMana build(Object... options) { + return new FreyaCrescentConditionalMana(this.mana); + } + + @Override + public String getRule() { + return "Spend this mana only to cast an Equipment spell or activate an equip ability"; + } +} + +class FreyaCrescentConditionalMana extends ConditionalMana { + + FreyaCrescentConditionalMana(Mana mana) { + super(mana); + addCondition(FreyaCrescentCondition.instance); + } +} + +enum FreyaCrescentCondition implements Condition { + instance; + + private static final FilterSpell filter = new FilterSpell("Equipment spells"); + + static { + filter.add(SubType.EQUIPMENT.getPredicate()); + } + + @Override + public boolean apply(Game game, Ability source) { + if (source.isActivated()) { + return false; + } + if (source instanceof EquipAbility) { + return true; + } + if (source instanceof SpellAbility) { + MageObject object = game.getObject(source); + if ((object instanceof StackObject)) { + return filter.match((StackObject) object, source.getControllerId(), source, game); + } + + // checking mana without real cast + if (game.inCheckPlayableState()) { + Spell spell = null; + if (object instanceof Card) { + spell = new Spell((Card) object, (SpellAbility) source, source.getControllerId(), game.getState().getZone(source.getSourceId()), game); + } else if (object instanceof Commander) { + spell = new Spell(((Commander) object).getSourceObject(), (SpellAbility) source, source.getControllerId(), game.getState().getZone(source.getSourceId()), game); + } + return filter.match(spell, source.getControllerId(), source, game); + } + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/f/FrodoDeterminedHero.java b/Mage.Sets/src/mage/cards/f/FrodoDeterminedHero.java index efb3a17d37b..f4e40dba5dc 100644 --- a/Mage.Sets/src/mage/cards/f/FrodoDeterminedHero.java +++ b/Mage.Sets/src/mage/cards/f/FrodoDeterminedHero.java @@ -29,6 +29,7 @@ public final class FrodoDeterminedHero extends CardImpl { = new FilterControlledPermanent("Equipment you control with mana value 2 or 3"); static { + filter.add(SubType.EQUIPMENT.getPredicate()); filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, 4)); filter.add(new ManaValuePredicate(ComparisonType.MORE_THAN, 1)); } diff --git a/Mage.Sets/src/mage/cards/f/FrontierSiege.java b/Mage.Sets/src/mage/cards/f/FrontierSiege.java index c18360d64e2..a05e0807877 100644 --- a/Mage.Sets/src/mage/cards/f/FrontierSiege.java +++ b/Mage.Sets/src/mage/cards/f/FrontierSiege.java @@ -16,7 +16,7 @@ import mage.cards.CardSetInfo; import mage.constants.*; import mage.filter.FilterPermanent; import mage.filter.StaticFilters; -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; @@ -30,10 +30,9 @@ import java.util.UUID; */ public final class FrontierSiege extends CardImpl { - private static final FilterPermanent filter = new FilterCreaturePermanent("a creature with flying you control"); + private static final FilterPermanent filter = new FilterControlledCreaturePermanent("a creature you control with flying"); static { - filter.add(TargetController.YOU.getControllerPredicate()); filter.add(new AbilityPredicate(FlyingAbility.class)); } @@ -96,6 +95,7 @@ class FrontierSiegeFightEffect extends OneShotEffect { FrontierSiegeFightEffect() { super(Outcome.Damage); + staticText = "have it fight target creature you don't control"; } private FrontierSiegeFightEffect(final FrontierSiegeFightEffect effect) { diff --git a/Mage.Sets/src/mage/cards/g/GalianBeast.java b/Mage.Sets/src/mage/cards/g/GalianBeast.java new file mode 100644 index 00000000000..9ff6b2f8107 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GalianBeast.java @@ -0,0 +1,51 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.common.DiesSourceTriggeredAbility; +import mage.abilities.effects.common.ReturnSourceFromGraveyardToBattlefieldEffect; +import mage.abilities.keyword.LifelinkAbility; +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 java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GalianBeast extends CardImpl { + + public GalianBeast(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.WEREWOLF); + this.subtype.add(SubType.BEAST); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + this.nightCard = true; + this.color.setBlack(true); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // When Galian Beast dies, return it to the battlefield tapped. + this.addAbility(new DiesSourceTriggeredAbility(new ReturnSourceFromGraveyardToBattlefieldEffect(true) + .setText("return it to the battlefield tapped"))); + } + + private GalianBeast(final GalianBeast card) { + super(card); + } + + @Override + public GalianBeast copy() { + return new GalianBeast(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GalufsFinalAct.java b/Mage.Sets/src/mage/cards/g/GalufsFinalAct.java new file mode 100644 index 00000000000..b0c0168951f --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GalufsFinalAct.java @@ -0,0 +1,47 @@ +package mage.cards.g; + +import mage.abilities.Ability; +import mage.abilities.common.DiesSourceTriggeredAbility; +import mage.abilities.dynamicvalue.common.SourcePermanentPowerValue; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +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 GalufsFinalAct extends CardImpl { + + public GalufsFinalAct(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{G}"); + + // Until end of turn, target creature gets +1/+0 and gains "When this creature dies, put a number of +1/+1 counters equal to its power on up to one target creature." + this.getSpellAbility().addEffect(new BoostTargetEffect(1, 0) + .setText("until end of turn, target creature gets +1/+0")); + Ability ability = new DiesSourceTriggeredAbility(new AddCountersTargetEffect( + CounterType.P1P1.createInstance(), SourcePermanentPowerValue.NOT_NEGATIVE + ).setText("put a number of +1/+1 counters equal to its power on up to one target creature")); + ability.addTarget(new TargetCreaturePermanent(0, 1)); + this.getSpellAbility().addEffect(new GainAbilityTargetEffect(ability, Duration.EndOfTurn) + .setText("and gains \"When this creature dies, put a number of +1/+1 counters " + + "equal to its power on up to one target creature.\"")); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + } + + private GalufsFinalAct(final GalufsFinalAct card) { + super(card); + } + + @Override + public GalufsFinalAct copy() { + return new GalufsFinalAct(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GalvanicDischarge.java b/Mage.Sets/src/mage/cards/g/GalvanicDischarge.java index 725f42f9893..2f91977e8f3 100644 --- a/Mage.Sets/src/mage/cards/g/GalvanicDischarge.java +++ b/Mage.Sets/src/mage/cards/g/GalvanicDischarge.java @@ -63,7 +63,7 @@ class GalvanicDischargeEffect extends OneShotEffect { return false; } new GetEnergyCountersControllerEffect(3).apply(game, source); - int numberToPay = controller.getAmount(0, controller.getCountersCount(CounterType.ENERGY), "How many {E} do you like to pay?", game); + int numberToPay = controller.getAmount(0, controller.getCountersCount(CounterType.ENERGY), "How many {E} do you like to pay?", source, game); if (numberToPay <= 0) { return true; } diff --git a/Mage.Sets/src/mage/cards/g/GandalfOfTheSecretFire.java b/Mage.Sets/src/mage/cards/g/GandalfOfTheSecretFire.java index b7b93aa887f..48b78922b53 100644 --- a/Mage.Sets/src/mage/cards/g/GandalfOfTheSecretFire.java +++ b/Mage.Sets/src/mage/cards/g/GandalfOfTheSecretFire.java @@ -5,12 +5,10 @@ import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.ReplacementEffectImpl; -import mage.abilities.effects.common.continuous.GainSuspendEffect; import mage.abilities.keyword.SuspendAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.counters.CounterType; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.ZoneChangeEvent; @@ -113,14 +111,8 @@ class GandalfOfTheSecretFireEffect extends ReplacementEffectImpl { if (controller == null || sourceSpell == null || sourceSpell.isCopy()) { return false; } - UUID exileId = SuspendAbility.getSuspendExileId(controller.getId(), game); - if (controller.moveCardsToExile(sourceSpell, source, game, true, exileId, "Suspended cards of " + controller.getName())) { - sourceSpell.addCounters(CounterType.TIME.createInstance(3), controller.getId(), source, game); - game.informPlayers(controller.getLogName() + " exiles " + sourceSpell.getLogName() + " with 3 time counters on it"); - } - if (!sourceSpell.getAbilities(game).containsClass(SuspendAbility.class)) { - game.addEffect(new GainSuspendEffect(new MageObjectReference(sourceSpell.getMainCard(), game)), source); - } + controller.moveCards(sourceSpell, Zone.EXILED, source, game); + SuspendAbility.addTimeCountersAndSuspend(sourceSpell.getMainCard(), 3, source, game); return true; } diff --git a/Mage.Sets/src/mage/cards/g/GateColossus.java b/Mage.Sets/src/mage/cards/g/GateColossus.java index ba900db8dd6..c83c89ec368 100644 --- a/Mage.Sets/src/mage/cards/g/GateColossus.java +++ b/Mage.Sets/src/mage/cards/g/GateColossus.java @@ -1,11 +1,10 @@ package mage.cards.g; import mage.MageInt; -import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.dynamicvalue.common.GateYouControlCount; +import mage.abilities.effects.common.AffinityEffect; import mage.abilities.effects.common.PutOnLibrarySourceEffect; -import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; import mage.abilities.hint.common.GateYouControlHint; import mage.abilities.keyword.DauntAbility; import mage.cards.CardImpl; @@ -22,7 +21,8 @@ import java.util.UUID; */ public final class GateColossus extends CardImpl { - private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.GATE, "a Gate"); + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.GATE, "Gates"); + private static final FilterControlledPermanent filter2 = new FilterControlledPermanent(SubType.GATE); public GateColossus(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{8}"); @@ -32,20 +32,17 @@ public final class GateColossus extends CardImpl { this.toughness = new MageInt(8); // This spell costs {1} less to cast for each Gate you control. - this.addAbility(new SimpleStaticAbility(Zone.ALL, - new SpellCostReductionForEachSourceEffect(1, GateYouControlCount.instance)) - .addHint(GateYouControlHint.instance) - ); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new AffinityEffect(filter)).addHint(GateYouControlHint.instance)); // Gate Colossus can't be blocked by creatures with power 2 or less. this.addAbility(new DauntAbility()); // Whenever a Gate you control enters, you may put Gate Colossus from your graveyard on top of your library. - this.addAbility(new EntersBattlefieldControlledTriggeredAbility( + this.addAbility(new EntersBattlefieldAllTriggeredAbility( Zone.GRAVEYARD, new PutOnLibrarySourceEffect( - true, "put {this} from your graveyard on top of your library" - ), filter, true + true, "put this card from your graveyard on top of your library" + ), filter2, true )); } diff --git a/Mage.Sets/src/mage/cards/g/GatekeeperOfMalakir.java b/Mage.Sets/src/mage/cards/g/GatekeeperOfMalakir.java index b45be20251f..856498f504f 100644 --- a/Mage.Sets/src/mage/cards/g/GatekeeperOfMalakir.java +++ b/Mage.Sets/src/mage/cards/g/GatekeeperOfMalakir.java @@ -1,36 +1,27 @@ - package mage.cards.g; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.condition.common.KickedCondition; -import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.common.SacrificeEffect; import mage.abilities.keyword.KickerAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.common.FilterControlledPermanent; +import mage.filter.StaticFilters; import mage.target.TargetPlayer; +import java.util.UUID; + /** - * * @author maurer.it_at_gmail.com */ public final class GatekeeperOfMalakir extends CardImpl { - private static final FilterControlledPermanent filter; - - static { - filter = new FilterControlledPermanent("creature"); - filter.add(CardType.CREATURE.getPredicate()); - } - public GatekeeperOfMalakir(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{B}{B}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{B}{B}"); this.subtype.add(SubType.VAMPIRE); this.subtype.add(SubType.WARRIOR); @@ -41,12 +32,11 @@ public final class GatekeeperOfMalakir extends CardImpl { this.addAbility(new KickerAbility("{B}")); // When this creature enters, if it was kicked, target player sacrifices a creature. - EntersBattlefieldTriggeredAbility ability = - new EntersBattlefieldTriggeredAbility(new SacrificeEffect(filter, 1, "target player")) - .setTriggerPhrase("When this creature enters, "); - Ability conditionalAbility = new ConditionalInterveningIfTriggeredAbility(ability, KickedCondition.ONCE, "When this creature enters, if it was kicked, target player sacrifices a creature."); - conditionalAbility.addTarget(new TargetPlayer()); - this.addAbility(conditionalAbility); + Ability ability = new EntersBattlefieldTriggeredAbility( + new SacrificeEffect(StaticFilters.FILTER_PERMANENT_CREATURE, 1, "target player") + ).withInterveningIf(KickedCondition.ONCE); + ability.addTarget(new TargetPlayer()); + this.addAbility(ability); } private GatekeeperOfMalakir(final GatekeeperOfMalakir card) { diff --git a/Mage.Sets/src/mage/cards/g/GauFeralYouth.java b/Mage.Sets/src/mage/cards/g/GauFeralYouth.java new file mode 100644 index 00000000000..27f682ac9bc --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GauFeralYouth.java @@ -0,0 +1,75 @@ +package mage.cards.g; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.dynamicvalue.common.SourcePermanentPowerValue; +import mage.abilities.effects.common.DamagePlayersEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +import mage.constants.*; +import mage.counters.CounterType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.game.Game; +import mage.watchers.common.CardsLeftGraveyardWatcher; + +/** + * @author balazskristof + */ +public final class GauFeralYouth extends CardImpl { + + public GauFeralYouth(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.BERSERKER); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Rage -- Whenever Gau attacks, put a +1/+1 counter on it. + this.addAbility(new AttacksTriggeredAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance())).withFlavorWord("Rage")); + + // At the beginning of each end step, if a card left your graveyard this turn, Gau deals damage equal to its power to each opponent. + this.addAbility( + new BeginningOfEndStepTriggeredAbility( + TargetController.ANY, + new DamagePlayersEffect(SourcePermanentPowerValue.NOT_NEGATIVE, TargetController.OPPONENT) + .setText("{this} deals damage equal to its power to each opponent"), + false, + GauFeralYouthCondition.instance + ), + new CardsLeftGraveyardWatcher() + ); + } + + private GauFeralYouth(final GauFeralYouth card) { + super(card); + } + + @Override + public GauFeralYouth copy() { + return new GauFeralYouth(this); + } +} + +enum GauFeralYouthCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + return !game + .getState() + .getWatcher(CardsLeftGraveyardWatcher.class) + .getCardsThatLeftGraveyard(source.getControllerId(), game) + .isEmpty(); + } + + @Override + public String toString() { + return "if a card left your graveyard this turn"; + } +} diff --git a/Mage.Sets/src/mage/cards/g/GaviNestWarden.java b/Mage.Sets/src/mage/cards/g/GaviNestWarden.java index d3b160ea39d..2a44541ccc5 100644 --- a/Mage.Sets/src/mage/cards/g/GaviNestWarden.java +++ b/Mage.Sets/src/mage/cards/g/GaviNestWarden.java @@ -93,13 +93,17 @@ class CyclingZeroCostEffect extends CostModificationEffectImpl { @Override public boolean apply(Game game, Ability source, Ability abilityToModify) { Player player = game.getPlayer(source.getControllerId()); - if (player == null || !player.chooseUse(outcome, "Pay {0} to cycle this card?", source, game)) { + if (player == null) { + return false; + } + if (game.inCheckPlayableState() || player.chooseUse(outcome, "Pay {0} to cycle this card?", source, game)) { + abilityToModify.clearManaCostsToPay(); + abilityToModify.getCosts().removeIf(cost -> !CyclingDiscardCost.class.isInstance(cost)); + abilityToModify.addManaCostsToPay(new GenericManaCost(0)); return true; } - abilityToModify.clearManaCostsToPay(); - abilityToModify.getCosts().removeIf(cost -> !CyclingDiscardCost.class.isInstance(cost)); - abilityToModify.addManaCostsToPay(new GenericManaCost(0)); - return true; + + return false; } @Override diff --git a/Mage.Sets/src/mage/cards/g/GeneralLeoCristophe.java b/Mage.Sets/src/mage/cards/g/GeneralLeoCristophe.java new file mode 100644 index 00000000000..d81a9a1e6f9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GeneralLeoCristophe.java @@ -0,0 +1,59 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.dynamicvalue.common.CreaturesYouControlCount; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.hint.common.CreaturesYouControlHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.FilterCard; +import mage.filter.common.FilterCreatureCard; +import mage.filter.predicate.mageobject.ManaValuePredicate; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GeneralLeoCristophe extends CardImpl { + + private static final FilterCard filter = new FilterCreatureCard("creature card with mana value 3 or less from your graveyard"); + + static { + filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, 4)); + } + + public GeneralLeoCristophe(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // When General Leo Cristophe enters, return up to one target creature card with mana value 3 or less from your graveyard to the battlefield. Then put a +1/+1 counter on General Leo Cristophe for each creature you control. + Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnFromGraveyardToBattlefieldTargetEffect()); + ability.addTarget(new TargetCardInYourGraveyard(0, 1, filter)); + ability.addEffect(new AddCountersSourceEffect(CounterType.P1P1.createInstance(), CreaturesYouControlCount.instance)); + this.addAbility(ability.addHint(CreaturesYouControlHint.instance)); + } + + private GeneralLeoCristophe(final GeneralLeoCristophe card) { + super(card); + } + + @Override + public GeneralLeoCristophe copy() { + return new GeneralLeoCristophe(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GenesisOfTheDaleks.java b/Mage.Sets/src/mage/cards/g/GenesisOfTheDaleks.java new file mode 100644 index 00000000000..daef4301cea --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GenesisOfTheDaleks.java @@ -0,0 +1,202 @@ +package mage.cards.g; + +import mage.abilities.Ability; +import mage.abilities.common.SagaAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DestroyAllEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.choices.FaceVillainousChoice; +import mage.choices.VillainousChoice; +import mage.constants.*; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.DalekToken; +import mage.players.Player; +import mage.target.common.TargetOpponent; +import mage.watchers.Watcher; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GenesisOfTheDaleks extends CardImpl { + + public GenesisOfTheDaleks(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{4}{B}{B}"); + + this.subtype.add(SubType.SAGA); + + // (As this Saga enters and after your draw step, add a lore counter. Sacrifice after IV.) + SagaAbility sagaAbility = new SagaAbility(this, SagaChapter.CHAPTER_IV); + + // I, II, III -- Create a 3/3 black Dalek artifact creature token with menace for each lore counter on Genesis of the Daleks. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_I, SagaChapter.CHAPTER_III, + new CreateTokenEffect(new DalekToken(), GenesisOfTheDaleksValue.instance) + ); + + // IV -- Target opponent faces a villainous choice -- Destroy all Dalek creatures and each of your opponents loses life equal to the total power of Daleks that died this turn, or destroy all non-Dalek creatures. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_IV, + new GenesisOfTheDaleksEffect(), new TargetOpponent() + ); + this.addAbility(sagaAbility, new GenesisOfTheDaleksWatcher()); + } + + private GenesisOfTheDaleks(final GenesisOfTheDaleks card) { + super(card); + } + + @Override + public GenesisOfTheDaleks copy() { + return new GenesisOfTheDaleks(this); + } +} + +enum GenesisOfTheDaleksValue implements DynamicValue { + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + Permanent permanent = sourceAbility.getSourcePermanentOrLKI(game); + if (permanent != null) { + return permanent + .getCounters(game) + .getCount(CounterType.LORE); + } + return Optional + .ofNullable(sourceAbility) + .map(Ability::getSourceId) + .map(game::getPermanentOrLKIBattlefield) + .map(p -> p.getCounters(game).getCount(CounterType.LORE)) + .orElse(0); + } + + @Override + public GenesisOfTheDaleksValue copy() { + return this; + } + + @Override + public String getMessage() { + return "lore counter on {this}"; + } + + @Override + public String toString() { + return "1"; + } +} + +class GenesisOfTheDaleksEffect extends OneShotEffect { + + private static final FaceVillainousChoice choice = new FaceVillainousChoice( + Outcome.LoseLife, new GenesisOfTheDaleksFirstChoice(), new GenesisOfTheDaleksSecondChoice() + ); + + GenesisOfTheDaleksEffect() { + super(Outcome.Benefit); + staticText = "target opponent " + choice.generateRule(); + } + + private GenesisOfTheDaleksEffect(final GenesisOfTheDaleksEffect effect) { + super(effect); + } + + @Override + public GenesisOfTheDaleksEffect copy() { + return new GenesisOfTheDaleksEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return Optional.ofNullable(getTargetPointer().getFirst(game, source)) + .map(game::getPlayer) + .map(player -> choice.faceChoice(player, game, source)) + .orElse(false); + } +} + +class GenesisOfTheDaleksFirstChoice extends VillainousChoice { + + private static final FilterPermanent filter = new FilterCreaturePermanent(SubType.DALEK, ""); + + GenesisOfTheDaleksFirstChoice() { + super("Destroy all Dalek creatures and each of your opponents loses life equal to the total power of Daleks that died this turn", "Destroy all Daleks and lose life"); + } + + @Override + public boolean doChoice(Player player, Game game, Ability source) { + new DestroyAllEffect(filter).apply(game, source); + game.processAction(); + int amount = game.getState().getWatcher(GenesisOfTheDaleksWatcher.class).getPower(); + if (amount > 0) { + for (UUID opponentId : game.getOpponents(source.getControllerId())) { + Optional.ofNullable(opponentId) + .map(game::getPlayer) + .ifPresent(opponent -> opponent.loseLife(amount, game, source, false)); + } + } + return true; + } +} + +class GenesisOfTheDaleksSecondChoice extends VillainousChoice { + + private static final FilterPermanent filter = new FilterCreaturePermanent(); + + static { + filter.add(Predicates.not(SubType.DALEK.getPredicate())); + } + + GenesisOfTheDaleksSecondChoice() { + super("destroy all non-Dalek creatures", "Destroy all non-Daleks"); + } + + @Override + public boolean doChoice(Player player, Game game, Ability source) { + return new DestroyAllEffect(filter).apply(game, source); + } +} + +class GenesisOfTheDaleksWatcher extends Watcher { + + private int power = 0; + + GenesisOfTheDaleksWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() != GameEvent.EventType.ZONE_CHANGE) { + return; + } + ZoneChangeEvent zEvent = (ZoneChangeEvent) event; + if (zEvent.isDiesEvent() && zEvent.getTarget().hasSubtype(SubType.DALEK, game)) { + power += zEvent.getTarget().getPower().getValue(); + } + } + + @Override + public void reset() { + super.reset(); + power = 0; + } + + public int getPower() { + return power; + } +} diff --git a/Mage.Sets/src/mage/cards/g/GilgameshMasterAtArms.java b/Mage.Sets/src/mage/cards/g/GilgameshMasterAtArms.java new file mode 100644 index 00000000000..3deb2f77af6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GilgameshMasterAtArms.java @@ -0,0 +1,186 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.MageItem; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.permanent.PermanentIdPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.TargetImpl; +import mage.target.TargetPermanent; +import mage.target.common.TargetCardInLibrary; +import mage.target.targetpointer.FixedTargets; +import mage.util.RandomUtil; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author TheElk801 + */ +public final class GilgameshMasterAtArms extends CardImpl { + + public GilgameshMasterAtArms(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{R}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SAMURAI); + this.power = new MageInt(6); + this.toughness = new MageInt(6); + + // Whenever Gilgamesh enters or attacks, look at the top six cards of your library. You may put any number of Equipment cards from among them onto the battlefield. Put the rest on the bottom of your library in a random order. When you put one or more Equipment onto the battlefield this way, you may attach one of them to a Samurai you control. + this.addAbility(new EntersBattlefieldOrAttacksSourceTriggeredAbility(new GilgameshMasterAtArmsLookEffect())); + } + + private GilgameshMasterAtArms(final GilgameshMasterAtArms card) { + super(card); + } + + @Override + public GilgameshMasterAtArms copy() { + return new GilgameshMasterAtArms(this); + } +} + +class GilgameshMasterAtArmsLookEffect extends OneShotEffect { + + private static final FilterCard filter = new FilterCard("Equipment cards"); + + static { + filter.add(SubType.EQUIPMENT.getPredicate()); + } + + GilgameshMasterAtArmsLookEffect() { + super(Outcome.Benefit); + staticText = "look at the top six cards of your library. You may put any number of Equipment cards " + + "from among them onto the battlefield. Put the rest on the bottom of your library in a random order. " + + "When you put one or more Equipment onto the battlefield this way, you may attach one of them to a Samurai you control"; + } + + private GilgameshMasterAtArmsLookEffect(final GilgameshMasterAtArmsLookEffect effect) { + super(effect); + } + + @Override + public GilgameshMasterAtArmsLookEffect copy() { + return new GilgameshMasterAtArmsLookEffect(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, 6)); + cards.retainZone(Zone.LIBRARY, game); + if (cards.isEmpty()) { + return false; + } + TargetCard target = new TargetCardInLibrary(0, Integer.MAX_VALUE, filter); + player.choose(outcome, cards, target, source, game); + Cards toPlay = new CardsImpl(target.getTargets()); + player.moveCards(toPlay, Zone.BATTLEFIELD, source, game); + cards.retainZone(Zone.LIBRARY, game); + player.putCardsOnBottomOfLibrary(cards, game, source, false); + Set permanents = toPlay + .stream() + .map(game::getPermanent) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + if (!permanents.isEmpty()) { + game.fireReflexiveTriggeredAbility(new ReflexiveTriggeredAbility( + new GilgameshMasterAtArmsAttachEffect(permanents, game), false + ), source); + } + return true; + } +} + +class GilgameshMasterAtArmsAttachEffect extends OneShotEffect { + + private static final FilterPermanent filter = new FilterControlledPermanent(SubType.SAMURAI); + + GilgameshMasterAtArmsAttachEffect(Collection permanents, Game game) { + super(Outcome.Benefit); + staticText = "you may attach one of them to a Samurai you control"; + this.setTargetPointer(new FixedTargets(permanents, game)); + } + + private GilgameshMasterAtArmsAttachEffect(final GilgameshMasterAtArmsAttachEffect effect) { + super(effect); + } + + @Override + public GilgameshMasterAtArmsAttachEffect copy() { + return new GilgameshMasterAtArmsAttachEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Set permanents = this + .getTargetPointer() + .getTargets(game, source) + .stream() + .map(game::getPermanent) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + if (player == null || permanents.isEmpty()) { + return false; + } + Permanent samurai = Optional + .of(new TargetPermanent(0, 1, filter, true)) + .map(t -> { + player.choose(outcome, t, source, game); + return t; + }) + .map(TargetImpl::getFirstTarget) + .map(game::getPermanent) + .orElse(null); + if (samurai == null) { + return false; + } + switch (permanents.size()) { + case 0: + return false; + case 1: + return samurai.addAttachment(RandomUtil.randomFromCollection(permanents).getId(), source, game); + default: + break; + } + FilterPermanent filterPermanent = new FilterPermanent("Equipment"); + Predicates.or( + permanents + .stream() + .map(MageItem::getId) + .map(PermanentIdPredicate::new) + .collect(Collectors.toSet()) + ); + return Optional + .of(new TargetPermanent(0, 1, filterPermanent, true)) + .map(t -> { + player.choose(outcome, t, source, game); + return t; + }) + .map(TargetImpl::getFirstTarget) + .map(game::getPermanent) + .map(equipment -> samurai.addAttachment(equipment.getId(), source, game)) + .orElse(false); + } +} diff --git a/Mage.Sets/src/mage/cards/g/Gingerbrute.java b/Mage.Sets/src/mage/cards/g/Gingerbrute.java index b3b92fcc97c..c1fd73dc4b4 100644 --- a/Mage.Sets/src/mage/cards/g/Gingerbrute.java +++ b/Mage.Sets/src/mage/cards/g/Gingerbrute.java @@ -46,7 +46,7 @@ public final class Gingerbrute extends CardImpl { )); // {2}, {T}, Sacrifice Gingerbrute: You gain 3 life. - this.addAbility(new FoodAbility(true)); + this.addAbility(new FoodAbility()); } private Gingerbrute(final Gingerbrute card) { diff --git a/Mage.Sets/src/mage/cards/g/GlissaSunslayer.java b/Mage.Sets/src/mage/cards/g/GlissaSunslayer.java index 5996a730353..13b5de6d9c4 100644 --- a/Mage.Sets/src/mage/cards/g/GlissaSunslayer.java +++ b/Mage.Sets/src/mage/cards/g/GlissaSunslayer.java @@ -104,7 +104,7 @@ class GlissaSunslayerEffect extends OneShotEffect { permanent.removeCounters(counterName, 1, source, game); removed++; } else { - int amount = controller.getAmount(1, Math.min(permanent.getCounters(game).get(counterName).getCount(), toRemove - removed), "How many?", game); + int amount = controller.getAmount(1, Math.min(permanent.getCounters(game).get(counterName).getCount(), toRemove - removed), "How many?", source, game); if (amount > 0) { removed += amount; permanent.removeCounters(counterName, amount, source, game); diff --git a/Mage.Sets/src/mage/cards/g/GoblinGame.java b/Mage.Sets/src/mage/cards/g/GoblinGame.java index 11586eef0ea..37711f5ff42 100644 --- a/Mage.Sets/src/mage/cards/g/GoblinGame.java +++ b/Mage.Sets/src/mage/cards/g/GoblinGame.java @@ -68,7 +68,7 @@ class GoblinGameEffect extends OneShotEffect { .collect(Collectors.toList()); for (Player player : players) { // TODO: consider changing 1000 to another cap, or even Integer.MAX_VALUE if the Volcano Hellion binary wraparound gets addressed (although hiding over two billions of items would be rather difficult IRL) - numberChosen.put(player.getId(), player.getAmount(1, 1000, "Choose a number of objects to hide.", game)); + numberChosen.put(player.getId(), player.getAmount(1, 1000, "Choose a number of objects to hide.", source, game)); } // get lowest number diff --git a/Mage.Sets/src/mage/cards/g/GoblinWarCry.java b/Mage.Sets/src/mage/cards/g/GoblinWarCry.java index 243edcf07cc..f7e38088f60 100644 --- a/Mage.Sets/src/mage/cards/g/GoblinWarCry.java +++ b/Mage.Sets/src/mage/cards/g/GoblinWarCry.java @@ -67,10 +67,7 @@ class GoblinWarCryEffect extends OneShotEffect { FilterCreaturePermanent filter = new FilterCreaturePermanent("creature you control"); filter.add(new ControllerIdPredicate(player.getId())); Target target = new TargetPermanent(1, 1, filter, true); - if (target.canChoose(player.getId(), source, game)) { - while (!target.isChosen(game) && target.canChoose(player.getId(), source, game) && player.canRespond()) { - player.chooseTarget(Outcome.DestroyPermanent, target, source, game); - } + if (player.chooseTarget(Outcome.DestroyPermanent, target, source, game)) { Permanent permanent = game.getPermanent(target.getFirstTarget()); if (permanent != null) { game.informPlayers(player.getLogName() + " has chosen " + permanent.getLogName() + " as their only creature able to block this turn"); diff --git a/Mage.Sets/src/mage/cards/g/GogoMysteriousMime.java b/Mage.Sets/src/mage/cards/g/GogoMysteriousMime.java new file mode 100644 index 00000000000..9fc8b9a2e7b --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GogoMysteriousMime.java @@ -0,0 +1,98 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.combat.AttacksIfAbleTargetEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; +import mage.target.targetpointer.FixedTargets; +import mage.target.targetpointer.TargetPointer; +import mage.util.functions.CopyApplier; + +import java.util.Arrays; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GogoMysteriousMime extends CardImpl { + + public GogoMysteriousMime(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // At the beginning of combat on your turn, you may have Gogo become a copy of another target creature you control until end of turn, except its name is Gogo, Mysterious Mime. If you do, Gogo and that creature each get +2/+0 and gain haste until end of turn and attack this turn if able. + Ability ability = new BeginningOfCombatTriggeredAbility(new GogoMysteriousMimeEffect(), true); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_ANOTHER_TARGET_CREATURE_YOU_CONTROL)); + this.addAbility(ability); + } + + private GogoMysteriousMime(final GogoMysteriousMime card) { + super(card); + } + + @Override + public GogoMysteriousMime copy() { + return new GogoMysteriousMime(this); + } +} + +class GogoMysteriousMimeEffect extends OneShotEffect { + + private static final CopyApplier applier = new CopyApplier() { + @Override + public boolean apply(Game game, MageObject blueprint, Ability source, UUID copyToObjectId) { + blueprint.setName("Gogo, Mysterious Mime"); + return true; + } + }; + + GogoMysteriousMimeEffect() { + super(Outcome.Benefit); + staticText = "have {this} become a copy of another target creature you control until end of turn, " + + "except its name is Gogo, Mysterious Mime. If you do, {this} and that creature " + + "each get +2/+0 and gain haste until end of turn and attack this turn if able"; + } + + private GogoMysteriousMimeEffect(final GogoMysteriousMimeEffect effect) { + super(effect); + } + + @Override + public GogoMysteriousMimeEffect copy() { + return new GogoMysteriousMimeEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent sourcePermanent = source.getSourcePermanentIfItStillExists(game); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (sourcePermanent == null || permanent == null) { + return false; + } + game.copyPermanent(Duration.EndOfTurn, permanent, sourcePermanent.getId(), source, applier); + TargetPointer targetPointer = new FixedTargets(Arrays.asList(sourcePermanent, permanent), game); + game.addEffect(new BoostTargetEffect(2, 0) + .setTargetPointer(targetPointer.copy()), source); + game.addEffect(new GainAbilityTargetEffect(HasteAbility.getInstance()) + .setTargetPointer(targetPointer.copy()), source); + game.addEffect(new AttacksIfAbleTargetEffect(Duration.EndOfTurn) + .setTargetPointer(targetPointer.copy()), source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/g/GoldenEgg.java b/Mage.Sets/src/mage/cards/g/GoldenEgg.java index 6aa2dba566c..8bb4f7ec523 100644 --- a/Mage.Sets/src/mage/cards/g/GoldenEgg.java +++ b/Mage.Sets/src/mage/cards/g/GoldenEgg.java @@ -34,7 +34,7 @@ public final class GoldenEgg extends CardImpl { this.addAbility(ability); // {2}, {T}, Sacrifice Golden Egg: You gain 3 life. - this.addAbility(new FoodAbility(true)); + this.addAbility(new FoodAbility()); } private GoldenEgg(final GoldenEgg card) { diff --git a/Mage.Sets/src/mage/cards/g/GorMuldrakAmphinologist.java b/Mage.Sets/src/mage/cards/g/GorMuldrakAmphinologist.java index 053711606b3..0de2f5d0c8b 100644 --- a/Mage.Sets/src/mage/cards/g/GorMuldrakAmphinologist.java +++ b/Mage.Sets/src/mage/cards/g/GorMuldrakAmphinologist.java @@ -2,12 +2,12 @@ package mage.cards.g; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; import mage.abilities.effects.common.continuous.GainAbilityControllerEffect; import mage.abilities.keyword.ProtectionAbility; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; @@ -45,7 +45,7 @@ public final class GorMuldrakAmphinologist extends CardImpl { new GainAbilityControllerEffect(new ProtectionAbility(filter)).setText("you") ); ability.addEffect(new GainAbilityControlledEffect( - new ProtectionAbility(filter), Duration.WhileOnBattlefield + new ProtectionAbility(filter), Duration.WhileOnBattlefield, StaticFilters.FILTER_PERMANENTS ).concatBy("and")); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/g/GorehornRaider.java b/Mage.Sets/src/mage/cards/g/GorehornRaider.java index c148f0aca00..c3c39a00716 100644 --- a/Mage.Sets/src/mage/cards/g/GorehornRaider.java +++ b/Mage.Sets/src/mage/cards/g/GorehornRaider.java @@ -31,6 +31,7 @@ public final class GorehornRaider extends CardImpl { // Raid -- When this creature enters, if you attacked this turn, this creature deals 2 damage to any target. Ability ability = new EntersBattlefieldTriggeredAbility(new DamageTargetEffect(2)) + .withRuleTextReplacement(false) .withInterveningIf(RaidCondition.instance); ability.addTarget(new TargetAnyTarget()); this.addAbility(ability.setAbilityWord(AbilityWord.RAID).addHint(RaidHint.instance), new PlayerAttackedWatcher()); diff --git a/Mage.Sets/src/mage/cards/g/GourmandsTalent.java b/Mage.Sets/src/mage/cards/g/GourmandsTalent.java index 71c95175598..00339144a18 100644 --- a/Mage.Sets/src/mage/cards/g/GourmandsTalent.java +++ b/Mage.Sets/src/mage/cards/g/GourmandsTalent.java @@ -97,7 +97,7 @@ class GourmandsTalentEffect extends ContinuousEffectImpl { permanent.addSubType(game, SubType.FOOD); break; case AbilityAddingRemovingEffects_6: - permanent.addAbility(new FoodAbility(true), source.getSourceId(), game); + permanent.addAbility(new FoodAbility(), source.getSourceId(), game); break; } } @@ -113,4 +113,4 @@ class GourmandsTalentEffect extends ContinuousEffectImpl { public boolean hasLayer(Layer layer) { return layer == Layer.TypeChangingEffects_4 || layer == Layer.AbilityAddingRemovingEffects_6; } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/g/GratuitousViolence.java b/Mage.Sets/src/mage/cards/g/GratuitousViolence.java index bddff4d71dd..b7782e25594 100644 --- a/Mage.Sets/src/mage/cards/g/GratuitousViolence.java +++ b/Mage.Sets/src/mage/cards/g/GratuitousViolence.java @@ -43,7 +43,7 @@ class GratuitousViolenceReplacementEffect extends ReplacementEffectImpl { GratuitousViolenceReplacementEffect() { super(Duration.WhileOnBattlefield, Outcome.Damage); - staticText = "If a creature you control would deal damage to a permanent or player, it deals double that damage to that permanent or player instead"; + staticText = "If a creature you control would deal damage to a permanent or player, it deals double that damage instead"; } private GratuitousViolenceReplacementEffect(final GratuitousViolenceReplacementEffect effect) { diff --git a/Mage.Sets/src/mage/cards/g/Gravecrawler.java b/Mage.Sets/src/mage/cards/g/Gravecrawler.java index 0fde6276bfe..cc429b564d4 100644 --- a/Mage.Sets/src/mage/cards/g/Gravecrawler.java +++ b/Mage.Sets/src/mage/cards/g/Gravecrawler.java @@ -55,7 +55,7 @@ class GravecrawlerPlayEffect extends AsThoughEffectImpl { public GravecrawlerPlayEffect() { super(AsThoughEffectType.CAST_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfGame, Outcome.Benefit); - staticText = "You may cast {this} from your graveyard as long as you control a Zombie"; + staticText = "You may cast this card from your graveyard as long as you control a Zombie"; } private GravecrawlerPlayEffect(final GravecrawlerPlayEffect effect) { @@ -85,4 +85,4 @@ class GravecrawlerPlayEffect extends AsThoughEffectImpl { return false; } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/g/GreenwheelLiberator.java b/Mage.Sets/src/mage/cards/g/GreenwheelLiberator.java index 2934e000426..130582d805a 100644 --- a/Mage.Sets/src/mage/cards/g/GreenwheelLiberator.java +++ b/Mage.Sets/src/mage/cards/g/GreenwheelLiberator.java @@ -1,19 +1,20 @@ package mage.cards.g; -import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldAbility; import mage.abilities.condition.common.RevoltCondition; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.constants.AbilityWord; import mage.constants.CardType; import mage.constants.SubType; import mage.counters.CounterType; import mage.watchers.common.RevoltWatcher; +import java.util.UUID; + /** * @author JRHerlehy */ @@ -29,11 +30,11 @@ public final class GreenwheelLiberator extends CardImpl { // Revolt — Greenbelt Liberator enters the battlefield with two +1/+1 counters on it if a // permanent you controlled left the battlefield this turn. - Ability ability = new EntersBattlefieldAbility( - new AddCountersSourceEffect(CounterType.P1P1.createInstance(2)), false, RevoltCondition.instance, - "Revolt — {this} enters with two +1/+1 counters on it if a permanent you controlled left the battlefield this turn.", null); - ability.addWatcher(new RevoltWatcher()); - this.addAbility(ability.addHint(RevoltCondition.getHint())); + this.addAbility(new EntersBattlefieldAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance(2)), false, + RevoltCondition.instance, "{this} enters with two +1/+1 counters on it " + + "if a permanent you controlled left the battlefield this turn.", null + ).setAbilityWord(AbilityWord.REVOLT).addHint(RevoltCondition.getHint()), new RevoltWatcher()); } private GreenwheelLiberator(final GreenwheelLiberator card) { diff --git a/Mage.Sets/src/mage/cards/g/GrenzoHavocRaiser.java b/Mage.Sets/src/mage/cards/g/GrenzoHavocRaiser.java index 6ef763df4e2..755c7b2dc10 100644 --- a/Mage.Sets/src/mage/cards/g/GrenzoHavocRaiser.java +++ b/Mage.Sets/src/mage/cards/g/GrenzoHavocRaiser.java @@ -124,7 +124,7 @@ class GrenzoHavocRaiserEffect extends OneShotEffect { GrenzoHavocRaiserEffect() { super(Outcome.PutCreatureInPlay); - this.staticText = "exile the top card of that player's library. 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"; + this.staticText = "exile the top card of that player's library. Until end of turn, you may cast that card and you may spend mana as though it were mana of any color to cast that spell"; } private GrenzoHavocRaiserEffect(final GrenzoHavocRaiserEffect effect) { diff --git a/Mage.Sets/src/mage/cards/g/GuidelightPathmaker.java b/Mage.Sets/src/mage/cards/g/GuidelightPathmaker.java index 43a1fcfdf95..eac210fa75a 100644 --- a/Mage.Sets/src/mage/cards/g/GuidelightPathmaker.java +++ b/Mage.Sets/src/mage/cards/g/GuidelightPathmaker.java @@ -59,7 +59,7 @@ class GuidelightPathmakerEffect extends OneShotEffect { super(Outcome.Benefit); staticText = "search your library for an artifact card and reveal it. " + "Put it onto the battlefield if its mana value is 2 or less. " + - "Otherwise, put it into your hand. Then shuffle"; + "Otherwise, put it into your hand. If you search your library this way, shuffle"; } private GuidelightPathmakerEffect(final GuidelightPathmakerEffect effect) { diff --git a/Mage.Sets/src/mage/cards/h/HadesSorcererOfEld.java b/Mage.Sets/src/mage/cards/h/HadesSorcererOfEld.java new file mode 100644 index 00000000000..46935cb71a0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HadesSorcererOfEld.java @@ -0,0 +1,50 @@ +package mage.cards.h; + +import java.util.UUID; +import mage.MageInt; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.abilities.keyword.VigilanceAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.replacement.GraveyardFromAnywhereExileReplacementEffect; +import mage.abilities.effects.common.ruleModifying.PlayFromGraveyardControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; + +/** + * @author balazskristof + */ +public final class HadesSorcererOfEld extends CardImpl { + + public HadesSorcererOfEld(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.AVATAR); + this.power = new MageInt(6); + this.toughness = new MageInt(6); + + this.color.setBlue(true); + this.color.setBlack(true); + this.nightCard = true; + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Echo of the Lost -- During your turn you may play cards from your graveyard. + this.addAbility(new SimpleStaticAbility(PlayFromGraveyardControllerEffect.playCards()).withFlavorWord("Echo of the Lost")); + + // If a card or token would be put into your graveyard from anywhere, exile it instead. + this.addAbility(new SimpleStaticAbility(new GraveyardFromAnywhereExileReplacementEffect(true, true))); + } + + private HadesSorcererOfEld(final HadesSorcererOfEld card) { + super(card); + } + + @Override + public HadesSorcererOfEld copy() { + return new HadesSorcererOfEld(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HaloForager.java b/Mage.Sets/src/mage/cards/h/HaloForager.java index 53602eb6b58..f657cef2630 100644 --- a/Mage.Sets/src/mage/cards/h/HaloForager.java +++ b/Mage.Sets/src/mage/cards/h/HaloForager.java @@ -78,7 +78,7 @@ class HaloForagerPayEffect extends OneShotEffect { if (player == null || !player.chooseUse(outcome, "Pay " + cost.getText() + "?", source, game)) { return false; } - int costX = player.announceXMana(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source); + int costX = player.announceX(0, Integer.MAX_VALUE, "Announce the value for {X} (pay to free cast)", game, source, true); cost.add(new GenericManaCost(costX)); if (!cost.pay(source, game, source, source.getControllerId(), false, null)) { return false; diff --git a/Mage.Sets/src/mage/cards/h/HarnessedLightning.java b/Mage.Sets/src/mage/cards/h/HarnessedLightning.java index baf4f978f17..02967cf2df6 100644 --- a/Mage.Sets/src/mage/cards/h/HarnessedLightning.java +++ b/Mage.Sets/src/mage/cards/h/HarnessedLightning.java @@ -62,7 +62,7 @@ class HarnessedLightningEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { new GetEnergyCountersControllerEffect(3).apply(game, source); - int numberToPay = controller.getAmount(0, controller.getCountersCount(CounterType.ENERGY), "How many {E} do you like to pay?", game); + int numberToPay = controller.getAmount(0, controller.getCountersCount(CounterType.ENERGY), "How many {E} do you like to pay?", source, game); if (numberToPay > 0) { Cost cost = new PayEnergyCost(numberToPay); if (cost.pay(source, game, source, source.getControllerId(), true)) { diff --git a/Mage.Sets/src/mage/cards/h/HaviTheAllFather.java b/Mage.Sets/src/mage/cards/h/HaviTheAllFather.java new file mode 100644 index 00000000000..e79e460bb93 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HaviTheAllFather.java @@ -0,0 +1,103 @@ +package mage.cards.h; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.DiesThisOrAnotherTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.CardsInControllerGraveyardCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.abilities.keyword.IndestructibleAbility; +import mage.cards.Card; +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.StaticFilters; +import mage.filter.common.FilterCreatureCard; +import mage.filter.common.FilterHistoricCard; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetCardInYourGraveyard; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HaviTheAllFather extends CardImpl { + + private static final Condition condition = new CardsInControllerGraveyardCondition( + 4, new FilterHistoricCard("historic cards") + ); + private static final Hint hint = new ValueHint( + "Historic cards in your graveyard", new CardsInControllerGraveyardCount(new FilterHistoricCard()) + ); + private static final FilterCard filter = new FilterCreatureCard("legendary creature card with lesser mana value from your graveyard"); + + static { + filter.add(SuperType.LEGENDARY.getPredicate()); + filter.add(HaviTheAllFatherPredicate.instance); + } + + public HaviTheAllFather(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}{G}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.GOD); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(6); + this.toughness = new MageInt(6); + + // Havi, the All-Father has indestructible as long as there are four or more historic cards in your graveyard. + this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( + new GainAbilitySourceEffect(IndestructibleAbility.getInstance()), condition, + "{this} has indestructible as long as there are four or more historic cards in your graveyard" + )).addHint(hint)); + + // Sage Project -- Whenever Havi or another legendary creature you control dies, return target legendary creature card with lesser mana value from your graveyard to the battlefield tapped. + Ability ability = new DiesThisOrAnotherTriggeredAbility( + new ReturnFromGraveyardToBattlefieldTargetEffect(true), + false, StaticFilters.FILTER_CONTROLLED_CREATURE_LEGENDARY + ); + ability.addTarget(new TargetCardInYourGraveyard(filter)); + this.addAbility(ability.withFlavorWord("Sage Project")); + } + + private HaviTheAllFather(final HaviTheAllFather card) { + super(card); + } + + @Override + public HaviTheAllFather copy() { + return new HaviTheAllFather(this); + } +} + +enum HaviTheAllFatherPredicate implements ObjectSourcePlayerPredicate { + instance; + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + return input + .getObject() + .getManaValue() + < CardUtil + .getEffectValueFromAbility( + input.getSource(), "creatureDied", Permanent.class + ) + .map(MageObject::getManaValue) + .orElse(0); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HeapedHarvest.java b/Mage.Sets/src/mage/cards/h/HeapedHarvest.java index 55d52307517..a1e5fe4725c 100644 --- a/Mage.Sets/src/mage/cards/h/HeapedHarvest.java +++ b/Mage.Sets/src/mage/cards/h/HeapedHarvest.java @@ -33,7 +33,7 @@ public final class HeapedHarvest extends CardImpl { )); // {2}, {T}, Sacrifice Heaped Harvest: You gain 3 life. - this.addAbility(new FoodAbility(true)); + this.addAbility(new FoodAbility()); } private HeapedHarvest(final HeapedHarvest card) { diff --git a/Mage.Sets/src/mage/cards/h/HeartlessAct.java b/Mage.Sets/src/mage/cards/h/HeartlessAct.java index 8d487fd7fb8..7451941044b 100644 --- a/Mage.Sets/src/mage/cards/h/HeartlessAct.java +++ b/Mage.Sets/src/mage/cards/h/HeartlessAct.java @@ -90,7 +90,7 @@ class HeartlessActEffect extends OneShotEffect { permanent.removeCounters(counterName, 1, source, game); removed++; } else { - int amount = controller.getAmount(1, Math.min(permanent.getCounters(game).get(counterName).getCount(), toRemove - removed), "How many?", game); + int amount = controller.getAmount(1, Math.min(permanent.getCounters(game).get(counterName).getCount(), toRemove - removed), "How many?", source, game); if (amount > 0) { removed += amount; permanent.removeCounters(counterName, amount, source, game); diff --git a/Mage.Sets/src/mage/cards/h/HeideggerShinraExecutive.java b/Mage.Sets/src/mage/cards/h/HeideggerShinraExecutive.java new file mode 100644 index 00000000000..7a0b6ecc1fd --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HeideggerShinraExecutive.java @@ -0,0 +1,114 @@ +package mage.cards.h; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +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.filter.common.FilterControlledPermanent; +import mage.game.Controllable; +import mage.game.Game; +import mage.game.permanent.token.SoldierToken; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.Map; +import java.util.UUID; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * @author TheElk801 + */ +public final class HeideggerShinraExecutive extends CardImpl { + + private static final DynamicValue xValue = new PermanentsOnBattlefieldCount( + new FilterControlledPermanent(SubType.SOLDIER, "Soldiers you control"), null + ); + private static final Hint hint = new ValueHint("Soldiers you control", xValue); + + public HeideggerShinraExecutive(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // At the beginning of combat on your turn, target creature you control gets +X/+0 until end of turn, where X is the number of Soldiers you control. + Ability ability = new BeginningOfCombatTriggeredAbility(new BoostTargetEffect(xValue, StaticValue.get(0))); + ability.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(ability); + + // At the beginning of your end step, create a number of 1/1 white Soldier creature tokens equal to the number of opponents who control more creatures than you. + this.addAbility(new BeginningOfEndStepTriggeredAbility( + new CreateTokenEffect(new SoldierToken(), HeideggerShinraExecutiveValue.instance) + .setText("create a number of 1/1 white Soldier creature tokens equal " + + "to the number of opponents who control more creatures than you") + ).addHint(HeideggerShinraExecutiveValue.getHint())); + } + + private HeideggerShinraExecutive(final HeideggerShinraExecutive card) { + super(card); + } + + @Override + public HeideggerShinraExecutive copy() { + return new HeideggerShinraExecutive(this); + } +} + +enum HeideggerShinraExecutiveValue implements DynamicValue { + instance; + private static final Hint hint = new ValueHint("Opponents with more creatures than you", instance); + + public static Hint getHint() { + return hint; + } + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + Map map = game + .getBattlefield() + .getActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, sourceAbility.getControllerId(), sourceAbility, game) + .stream() + .map(Controllable::getControllerId) + .collect(Collectors.toMap(Function.identity(), x -> 1, Integer::sum)); + int amount = map.getOrDefault(sourceAbility.getControllerId(), 0); + return game + .getOpponents(sourceAbility.getControllerId()) + .stream() + .mapToInt(uuid -> map.getOrDefault(uuid, 0)) + .filter(x -> x > amount) + .map(x -> 1) + .sum(); + } + + @Override + public HeideggerShinraExecutiveValue copy() { + return this; + } + + @Override + public String getMessage() { + return ""; + } + + @Override + public String toString() { + return "1"; + } +} diff --git a/Mage.Sets/src/mage/cards/h/Helitrooper.java b/Mage.Sets/src/mage/cards/h/Helitrooper.java new file mode 100644 index 00000000000..428b559b001 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/Helitrooper.java @@ -0,0 +1,60 @@ +package mage.cards.h; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.effects.common.cost.ReduceCostEquipTargetSourceEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterAttackingCreature; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Helitrooper extends CardImpl { + + private static final FilterPermanent filter = new FilterAttackingCreature("another target attacking creature"); + + static { + filter.add(AnotherPredicate.instance); + } + + public Helitrooper(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(1); + this.toughness = new MageInt(2); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever this creature attacks, another target attacking creature gains flying until end of turn. + Ability ability = new AttacksTriggeredAbility(new GainAbilityTargetEffect(FlyingAbility.getInstance())); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + + // Equip abilities you activate that target this creature cost {2} less to activate. + this.addAbility(new SimpleStaticAbility(new ReduceCostEquipTargetSourceEffect(2))); + } + + private Helitrooper(final Helitrooper card) { + super(card); + } + + @Override + public Helitrooper copy() { + return new Helitrooper(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HeraldOfIlharg.java b/Mage.Sets/src/mage/cards/h/HeraldOfIlharg.java new file mode 100644 index 00000000000..5fc87cc06be --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HeraldOfIlharg.java @@ -0,0 +1,95 @@ +package mage.cards.h; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +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.Outcome; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.counters.Counters; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.stack.Spell; +import mage.players.Player; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HeraldOfIlharg extends CardImpl { + + public HeraldOfIlharg(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}{G}"); + + this.subtype.add(SubType.BOAR); + this.subtype.add(SubType.BEAST); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Whenever you cast a creature spell, put two +1/+1 counters on Herald of Ilharg. If that spell has mana value 5 or greater, Herald of Ilharg deals damage equal to the number of counters on it to each opponent. + Ability ability = new SpellCastControllerTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance(2)), + StaticFilters.FILTER_SPELL_A_CREATURE, false + ); + ability.addEffect(new HeraldOfIlhargEffect()); + this.addAbility(ability); + } + + private HeraldOfIlharg(final HeraldOfIlharg card) { + super(card); + } + + @Override + public HeraldOfIlharg copy() { + return new HeraldOfIlharg(this); + } +} + +class HeraldOfIlhargEffect extends OneShotEffect { + + HeraldOfIlhargEffect() { + super(Outcome.Benefit); + staticText = "If that spell has mana value 5 or greater, " + + "{this} deals damage equal to the number of counters on it to each opponent"; + } + + private HeraldOfIlhargEffect(final HeraldOfIlhargEffect effect) { + super(effect); + } + + @Override + public HeraldOfIlhargEffect copy() { + return new HeraldOfIlhargEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + int count = Optional + .ofNullable(source.getSourcePermanentOrLKI(game)) + .map(permanent -> permanent.getCounters(game)) + .map(Counters::getTotalCount) + .orElse(0); + Spell spell = (Spell) getValue("spellCast"); + if (count < 1 || spell == null || spell.getManaValue() < 5) { + return false; + } + for (UUID playerId : game.getOpponents(source.getControllerId())) { + Player player = game.getPlayer(playerId); + if (player != null) { + player.damage(count, source, game); + } + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/h/HermesOverseerOfElpis.java b/Mage.Sets/src/mage/cards/h/HermesOverseerOfElpis.java new file mode 100644 index 00000000000..c0682875d2a --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HermesOverseerOfElpis.java @@ -0,0 +1,53 @@ +package mage.cards.h; + +import mage.MageInt; +import mage.abilities.common.AttacksWithCreaturesTriggeredAbility; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.keyword.ScryEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.game.permanent.token.BirdVigilanceToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HermesOverseerOfElpis extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent(SubType.BIRD, "Birds"); + + public HermesOverseerOfElpis(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.ELDER); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // Whenever you cast a noncreature spell, create a 1/1 blue Bird creature token with flying and vigilance. + this.addAbility(new SpellCastControllerTriggeredAbility( + new CreateTokenEffect(new BirdVigilanceToken()), + StaticFilters.FILTER_SPELL_A_NON_CREATURE, false + )); + + // Whenever you attack with one or more Birds, scry 2. + this.addAbility(new AttacksWithCreaturesTriggeredAbility(new ScryEffect(2), 1, filter)); + } + + private HermesOverseerOfElpis(final HermesOverseerOfElpis card) { + super(card); + } + + @Override + public HermesOverseerOfElpis copy() { + return new HermesOverseerOfElpis(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HeroOfLeinaTower.java b/Mage.Sets/src/mage/cards/h/HeroOfLeinaTower.java index 499e959a430..f043b94587d 100644 --- a/Mage.Sets/src/mage/cards/h/HeroOfLeinaTower.java +++ b/Mage.Sets/src/mage/cards/h/HeroOfLeinaTower.java @@ -70,7 +70,7 @@ class HeroOfLeinaTowerEffect extends OneShotEffect { Player you = game.getPlayer(source.getControllerId()); ManaCosts cost = new ManaCostsImpl<>("{X}"); if (you != null && you.chooseUse(Outcome.BoostCreature, "Do you want to to pay {X}?", source, game)) { - int costX = you.announceXMana(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source); + int costX = you.announceX(0, Integer.MAX_VALUE, "Announce the value for {X} (pay to add +1/+1 counters)", game, source, true); cost.add(new GenericManaCost(costX)); if (cost.pay(source, game, source, source.getControllerId(), false, null)) { Permanent sourcePermanent = game.getPermanent(source.getSourceId()); diff --git a/Mage.Sets/src/mage/cards/h/HexParasite.java b/Mage.Sets/src/mage/cards/h/HexParasite.java index 3013430cfd8..6e85c817548 100644 --- a/Mage.Sets/src/mage/cards/h/HexParasite.java +++ b/Mage.Sets/src/mage/cards/h/HexParasite.java @@ -84,7 +84,7 @@ class HexParasiteEffect extends OneShotEffect { permanent.removeCounters(counterName, 1, source, game); removed++; } else { - int amount = controller.getAmount(1, Math.min(permanent.getCounters(game).get(counterName).getCount(), toRemove - removed), "How many?", game); + int amount = controller.getAmount(1, Math.min(permanent.getCounters(game).get(counterName).getCount(), toRemove - removed), "How many?", source, game); if (amount > 0) { removed += amount; permanent.removeCounters(counterName, amount, source, game); diff --git a/Mage.Sets/src/mage/cards/h/HiddenHerbalists.java b/Mage.Sets/src/mage/cards/h/HiddenHerbalists.java index bf32ff78c05..54b854b9afe 100644 --- a/Mage.Sets/src/mage/cards/h/HiddenHerbalists.java +++ b/Mage.Sets/src/mage/cards/h/HiddenHerbalists.java @@ -1,21 +1,20 @@ - package mage.cards.h; -import java.util.UUID; import mage.MageInt; import mage.Mana; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.condition.common.RevoltCondition; -import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.mana.BasicManaEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.constants.AbilityWord; import mage.constants.CardType; import mage.constants.SubType; import mage.watchers.common.RevoltWatcher; +import java.util.UUID; + /** - * * @author Styxo */ public final class HiddenHerbalists extends CardImpl { @@ -29,13 +28,10 @@ public final class HiddenHerbalists extends CardImpl { this.toughness = new MageInt(2); // Revolt &mdash When Hidden Herbalists enters the battlefield, if a permanent you controlled left the battlefield this turn, add {G}{G}; - this.addAbility( - new ConditionalInterveningIfTriggeredAbility(new EntersBattlefieldTriggeredAbility( - new BasicManaEffect(Mana.GreenMana(2)), false), RevoltCondition.instance, - "Revolt — When {this} enters, if a permanent you controlled left" - + " the battlefield this turn, add {G}{G}.").addHint(RevoltCondition.getHint()), - new RevoltWatcher() - ); + this.addAbility(new EntersBattlefieldTriggeredAbility(new BasicManaEffect(Mana.GreenMana(2))) + .withInterveningIf(RevoltCondition.instance) + .setAbilityWord(AbilityWord.REVOLT) + .addHint(RevoltCondition.getHint()), new RevoltWatcher()); } private HiddenHerbalists(final HiddenHerbalists card) { diff --git a/Mage.Sets/src/mage/cards/h/HiddenStockpile.java b/Mage.Sets/src/mage/cards/h/HiddenStockpile.java index 41b81013265..36581aa4b73 100644 --- a/Mage.Sets/src/mage/cards/h/HiddenStockpile.java +++ b/Mage.Sets/src/mage/cards/h/HiddenStockpile.java @@ -1,25 +1,23 @@ package mage.cards.h; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.condition.common.RevoltCondition; import mage.abilities.costs.common.SacrificeTargetCost; import mage.abilities.costs.mana.GenericManaCost; -import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.keyword.ScryEffect; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.AbilityWord; import mage.constants.CardType; +import mage.filter.StaticFilters; import mage.game.permanent.token.ServoToken; import mage.watchers.common.RevoltWatcher; import java.util.UUID; -import mage.filter.StaticFilters; - /** * @author LevelX2 */ @@ -29,11 +27,10 @@ public final class HiddenStockpile extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{W}{B}"); // Revolt — At the beginning of your end step, if a permanent you controlled left the battlefield this turn, create a 1/1 colorless Servo artifact creature token. - this.addAbility(new ConditionalInterveningIfTriggeredAbility( - new BeginningOfEndStepTriggeredAbility(new CreateTokenEffect(new ServoToken())), - RevoltCondition.instance, "At the beginning of your end step, if a permanent you controlled " + - "left the battlefield this turn, create a 1/1 colorless Servo artifact creature token." - ).setAbilityWord(AbilityWord.REVOLT).addHint(RevoltCondition.getHint()), new RevoltWatcher()); + this.addAbility(new BeginningOfEndStepTriggeredAbility(new CreateTokenEffect(new ServoToken())) + .withInterveningIf(RevoltCondition.instance) + .setAbilityWord(AbilityWord.REVOLT) + .addHint(RevoltCondition.getHint()), new RevoltWatcher()); // {1}, Sacrifice a creature: Scry 1. Ability ability = new SimpleActivatedAbility(new ScryEffect(1, false), new GenericManaCost(1)); diff --git a/Mage.Sets/src/mage/cards/h/HildibrandManderville.java b/Mage.Sets/src/mage/cards/h/HildibrandManderville.java new file mode 100644 index 00000000000..4de6f6dd901 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HildibrandManderville.java @@ -0,0 +1,60 @@ +package mage.cards.h; + +import mage.MageInt; +import mage.abilities.common.DiesSourceTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.asthought.MayCastFromGraveyardAsAdventureEffect; +import mage.abilities.effects.common.continuous.BoostAllEffect; +import mage.cards.AdventureCard; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.game.permanent.token.ZombieToken; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class HildibrandManderville extends AdventureCard { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature tokens you control"); + + static { + filter.add(TokenPredicate.TRUE); + filter.add(TargetController.YOU.getControllerPredicate()); + } + + public HildibrandManderville(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, new CardType[]{CardType.INSTANT}, "{1}{W}", "Gentleman's Rise", "{2}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.DETECTIVE); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Creature tokens you control get +1/+1. + this.addAbility(new SimpleStaticAbility(new BoostAllEffect(1, 1, Duration.WhileOnBattlefield, filter, false))); + + // When Hildibrand Manderville dies, you may cast it from your graveyard as an Adventure until the end of your next turn. + this.addAbility(new DiesSourceTriggeredAbility(new MayCastFromGraveyardAsAdventureEffect())); + + // Gentleman's Rise + // Create a 2/2 black Zombie creature token. + this.getSpellCard().getSpellAbility().addEffect(new CreateTokenEffect(new ZombieToken())); + + this.finalizeAdventure(); + } + + private HildibrandManderville(final HildibrandManderville card) { + super(card); + } + + @Override + public HildibrandManderville copy() { + return new HildibrandManderville(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/Holochess.java b/Mage.Sets/src/mage/cards/h/Holochess.java index 56b60a3f26d..d2199032d9c 100644 --- a/Mage.Sets/src/mage/cards/h/Holochess.java +++ b/Mage.Sets/src/mage/cards/h/Holochess.java @@ -67,7 +67,7 @@ class HolochessEffect extends OneShotEffect { if (player == null || opponent == null) { return false; } - int chosenNumber = player.getAmount(0, 3, "Choose a number between 0 and 3", game); + int chosenNumber = player.getAmount(0, 3, "Choose a number between 0 and 3", source, game); List creaturesControlledByOpponent = game.getBattlefield().getActivePermanents( StaticFilters.FILTER_PERMANENT_CREATURES, opponent.getId(), game); if (chosenNumber < creaturesControlledByOpponent.size()) { diff --git a/Mage.Sets/src/mage/cards/h/HotPursuit.java b/Mage.Sets/src/mage/cards/h/HotPursuit.java index 12318235707..cdab65a8ddb 100644 --- a/Mage.Sets/src/mage/cards/h/HotPursuit.java +++ b/Mage.Sets/src/mage/cards/h/HotPursuit.java @@ -1,30 +1,25 @@ package mage.cards.h; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.condition.Condition; -import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.common.SuspectTargetEffect; import mage.abilities.effects.common.combat.GoadTargetEffect; import mage.abilities.effects.common.continuous.GainControlAllUntapGainHasteEffect; +import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; -import mage.constants.WatcherScope; import mage.filter.FilterPermanent; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.Predicates; import mage.filter.predicate.permanent.GoadedPredicate; import mage.filter.predicate.permanent.SuspectedPredicate; import mage.game.Game; -import mage.game.events.GameEvent; import mage.target.common.TargetOpponentsCreaturePermanent; -import mage.watchers.Watcher; +import mage.watchers.common.PlayerLostGameWatcher; -import java.util.HashSet; -import java.util.Set; import java.util.UUID; /** @@ -52,13 +47,8 @@ public final class HotPursuit extends CardImpl { this.addAbility(ability); // At the beginning of combat on your turn, if two or more players have lost the game, gain control of all goaded and/or suspected creatures until end of turn. Untap them. They gain haste until end of turn. - this.addAbility(new ConditionalInterveningIfTriggeredAbility( - new BeginningOfCombatTriggeredAbility( - new GainControlAllUntapGainHasteEffect(filter) - ), HotPursuitCondition.instance, "At the beginning of combat on your turn, " + - "if two or more players have lost the game, gain control of all goaded and/or " + - "suspected creatures until end of turn. Untap them. They gain haste until end of turn." - ), new HotPursuitWatcher()); + this.addAbility(new BeginningOfCombatTriggeredAbility(new GainControlAllUntapGainHasteEffect(filter)) + .withInterveningIf(HotPursuitCondition.instance), new PlayerLostGameWatcher()); } private HotPursuit(final HotPursuit card) { @@ -76,36 +66,11 @@ enum HotPursuitCondition implements Condition { @Override public boolean apply(Game game, Ability source) { - return HotPursuitWatcher.checkCondition(game); - } -} - -class HotPursuitWatcher extends Watcher { - - private final Set players = new HashSet<>(); - - HotPursuitWatcher() { - super(WatcherScope.GAME); + return PlayerLostGameWatcher.getCount(game) >= 2; } @Override - public void watch(GameEvent event, Game game) { - switch (event.getType()) { - case BEGINNING_PHASE_PRE: - if (game.getTurnNum() == 1) { - players.clear(); - } - return; - case LOST: - players.add(event.getPlayerId()); - } - } - - static boolean checkCondition(Game game) { - return game - .getState() - .getWatcher(HotPursuitWatcher.class) - .players - .size() >= 2; + public String toString() { + return "two or more players have lost the game"; } } diff --git a/Mage.Sets/src/mage/cards/h/HraesvelgrOfTheFirstBrood.java b/Mage.Sets/src/mage/cards/h/HraesvelgrOfTheFirstBrood.java new file mode 100644 index 00000000000..d9b052fecb4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HraesvelgrOfTheFirstBrood.java @@ -0,0 +1,66 @@ +package mage.cards.h; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.common.combat.CantBeBlockedTargetEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.meta.OrTriggeredAbility; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.keyword.WardAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.target.common.TargetCreaturePermanent; + +/** + * @author balazskristof + */ +public final class HraesvelgrOfTheFirstBrood extends CardImpl { + + public HraesvelgrOfTheFirstBrood(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.ELDER); + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Ward {2} + this.addAbility(new WardAbility(new ManaCostsImpl<>("{2}"))); + + // Shiva's Aid -- When Hraesvelgr enters and whenever you cast a noncreature spell, target creature gets +1/+0 until end of turn and can't be blocked this turn. + Ability ability = new OrTriggeredAbility( + Zone.BATTLEFIELD, new BoostTargetEffect(1, 0), + new EntersBattlefieldTriggeredAbility(null), + new SpellCastControllerTriggeredAbility(null, StaticFilters.FILTER_SPELL_A_NON_CREATURE, false) + ); + ability.addEffect(new CantBeBlockedTargetEffect().setText("can't be blocked this turn").concatBy("and")); + ability.addTarget(new TargetCreaturePermanent()); + this.addAbility(ability); + } + + private HraesvelgrOfTheFirstBrood(final HraesvelgrOfTheFirstBrood card) { + super(card); + } + + @Override + public HraesvelgrOfTheFirstBrood copy() { + return new HraesvelgrOfTheFirstBrood(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HuntedByTheFamily.java b/Mage.Sets/src/mage/cards/h/HuntedByTheFamily.java new file mode 100644 index 00000000000..6ba78ccc7e6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HuntedByTheFamily.java @@ -0,0 +1,122 @@ +package mage.cards.h; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenCopyTargetEffect; +import mage.abilities.effects.common.continuous.BecomesCreatureTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.choices.FaceVillainousChoice; +import mage.choices.VillainousChoice; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.custom.CreatureToken; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HuntedByTheFamily extends CardImpl { + + public HuntedByTheFamily(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{5}{U}{U}"); + + // Choose up to four target creatures you don't control. For each of them, that creature's controller faces a villainous choice -- That creature becomes a 1/1 white Human creature and loses all abilities, or you create a token that's a copy of it. + this.getSpellAbility().addEffect(new HuntedByTheFamilyEffect()); + this.getSpellAbility().addTarget(new TargetPermanent( + 0, 4, StaticFilters.FILTER_CREATURES_YOU_DONT_CONTROL + )); + } + + private HuntedByTheFamily(final HuntedByTheFamily card) { + super(card); + } + + @Override + public HuntedByTheFamily copy() { + return new HuntedByTheFamily(this); + } +} + +class HuntedByTheFamilyEffect extends OneShotEffect { + + HuntedByTheFamilyEffect() { + super(Outcome.Benefit); + staticText = "choose up to four target creatures you don't control. " + + "For each of them, that creature's controller faces a villainous choice — " + + "That creature becomes a 1/1 white Human creature and loses all abilities, " + + "or you create a token that's a copy of it"; + } + + private HuntedByTheFamilyEffect(final HuntedByTheFamilyEffect effect) { + super(effect); + } + + @Override + public HuntedByTheFamilyEffect copy() { + return new HuntedByTheFamilyEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + for (UUID targetId : getTargetPointer().getTargets(game, source)) { + Permanent permanent = game.getPermanent(targetId); + Player player = game.getPlayer(game.getControllerId(targetId)); + if (permanent == null || player == null) { + continue; + } + FaceVillainousChoice choice = new FaceVillainousChoice( + Outcome.DestroyPermanent, + new HuntedByTheFamilyFirstChoice(permanent), + new HuntedByTheFamilySecondChoice(permanent) + ); + choice.faceChoice(player, game, source); + } + return true; + } +} + +class HuntedByTheFamilyFirstChoice extends VillainousChoice { + + private final Permanent permanent; + + HuntedByTheFamilyFirstChoice(Permanent permanent) { + super("", permanent.getIdName() + " becomes a 1/1 white Human and loses all abilities"); + this.permanent = permanent; + } + + @Override + public boolean doChoice(Player player, Game game, Ability source) { + game.addEffect(new BecomesCreatureTargetEffect( + new CreatureToken(1, 1) + .withSubType(SubType.HUMAN) + .withColor("W"), + true, false, Duration.Custom + ).setTargetPointer(new FixedTarget(permanent, game)), source); + return true; + } +} + +class HuntedByTheFamilySecondChoice extends VillainousChoice { + + private final Permanent permanent; + + HuntedByTheFamilySecondChoice(Permanent permanent) { + super("", "{controller} creates a token that's a copy of " + permanent.getIdName()); + this.permanent = permanent; + } + + @Override + public boolean doChoice(Player player, Game game, Ability source) { + return new CreateTokenCopyTargetEffect().setSavedPermanent(permanent).apply(game, source); + } +} diff --git a/Mage.Sets/src/mage/cards/i/IceFlan.java b/Mage.Sets/src/mage/cards/i/IceFlan.java new file mode 100644 index 00000000000..4f559475574 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/IceFlan.java @@ -0,0 +1,51 @@ +package mage.cards.i; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.TapTargetEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.keyword.IslandcyclingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class IceFlan extends CardImpl { + + public IceFlan(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{U}{U}"); + + this.subtype.add(SubType.ELEMENTAL); + this.subtype.add(SubType.OOZE); + this.power = new MageInt(5); + this.toughness = new MageInt(4); + + // When this creature enters, tap target artifact or creature an opponent controls. Put a stun counter on it. + Ability ability = new EntersBattlefieldTriggeredAbility(new TapTargetEffect()); + ability.addEffect(new AddCountersTargetEffect(CounterType.STUN.createInstance()).setText("Put a stun counter on it")); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_OPPONENTS_PERMANENT_ARTIFACT_OR_CREATURE)); + this.addAbility(ability); + + // Islandcycling {2} + this.addAbility(new IslandcyclingAbility(new ManaCostsImpl<>("{2}"))); + } + + private IceFlan(final IceFlan card) { + super(card); + } + + @Override + public IceFlan copy() { + return new IceFlan(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/IceMagic.java b/Mage.Sets/src/mage/cards/i/IceMagic.java new file mode 100644 index 00000000000..95850e347cc --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/IceMagic.java @@ -0,0 +1,56 @@ +package mage.cards.i; + +import mage.abilities.Mode; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.PutOnTopOrBottomLibraryTargetEffect; +import mage.abilities.effects.common.ReturnToHandTargetEffect; +import mage.abilities.effects.common.ShuffleIntoLibraryTargetEffect; +import mage.abilities.keyword.TieredAbility; +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 IceMagic extends CardImpl { + + public IceMagic(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}"); + + // Tiered + this.addAbility(new TieredAbility(this)); + + // * Blizzard -- {0} -- Return target creature to its owner's hand. + this.getSpellAbility().addEffect(new ReturnToHandTargetEffect()); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + this.getSpellAbility().withFirstModeCost(new GenericManaCost(2)); + this.getSpellAbility().withFirstModeFlavorWord("Blizzard"); + + // * Blizzara -- {2} -- Target creature's owner puts it on their choice of the top or bottom of their library. + this.getSpellAbility().addMode(new Mode(new PutOnTopOrBottomLibraryTargetEffect(false)) + .addTarget(new TargetCreaturePermanent()) + .withCost(new GenericManaCost(2)) + .withFlavorWord("Blizzara")); + + // * Blizzaga -- {5}{U} -- Target creature's owner shuffles it into their library. + this.getSpellAbility().addMode(new Mode(new ShuffleIntoLibraryTargetEffect() + .setText("target creature's owner shuffles it into their library")) + .addTarget(new TargetCreaturePermanent()) + .withCost(new ManaCostsImpl<>("{5}{U}")) + .withFlavorWord("Blizzaga")); + } + + private IceMagic(final IceMagic card) { + super(card); + } + + @Override + public IceMagic copy() { + return new IceMagic(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/IcebreakerKraken.java b/Mage.Sets/src/mage/cards/i/IcebreakerKraken.java index 8355d1af71a..3440213a5c2 100644 --- a/Mage.Sets/src/mage/cards/i/IcebreakerKraken.java +++ b/Mage.Sets/src/mage/cards/i/IcebreakerKraken.java @@ -7,22 +7,25 @@ import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.Cost; import mage.abilities.costs.common.ReturnToHandChosenControlledPermanentCost; -import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; import mage.abilities.effects.Effect; +import mage.abilities.effects.common.AffinityEffect; import mage.abilities.effects.common.DontUntapInPlayersNextUntapStepAllEffect; import mage.abilities.effects.common.ReturnToHandSourceEffect; -import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; import mage.abilities.hint.Hint; import mage.abilities.hint.ValueHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.Zone; import mage.filter.StaticFilters; import mage.filter.common.FilterControlledLandPermanent; import mage.filter.common.FilterControlledPermanent; import mage.target.common.TargetControlledPermanent; import mage.target.common.TargetOpponent; + import java.util.UUID; /** @@ -30,15 +33,13 @@ import java.util.UUID; */ public final class IcebreakerKraken extends CardImpl { - private static final FilterControlledPermanent filter - = new FilterControlledLandPermanent("snow land you control"); + private static final FilterControlledPermanent filter = new FilterControlledLandPermanent("snow lands"); static { filter.add(SuperType.SNOW.getPredicate()); } - private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter); - private static final Hint hint = new ValueHint("Snow lands you control", xValue); + private static final Hint hint = new ValueHint("Snow lands you control", new PermanentsOnBattlefieldCount(filter)); public IcebreakerKraken(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{10}{U}{U}"); @@ -49,9 +50,7 @@ public final class IcebreakerKraken extends CardImpl { this.toughness = new MageInt(8); // This spell costs {1} less to cast for each snow land you control. - this.addAbility(new SimpleStaticAbility( - Zone.ALL, new SpellCostReductionForEachSourceEffect(1, xValue) - ).addHint(hint)); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new AffinityEffect(filter)).addHint(hint)); // When Icebreaker Kraken enters the battlefield, artifacts and creatures target opponent controls don't untap during that player's next untap step. Effect effect = new DontUntapInPlayersNextUntapStepAllEffect(StaticFilters.FILTER_PERMANENT_ARTIFACT_OR_CREATURE); @@ -74,4 +73,4 @@ public final class IcebreakerKraken extends CardImpl { public IcebreakerKraken copy() { return new IcebreakerKraken(this); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/i/IdrisSoulOfTheTARDIS.java b/Mage.Sets/src/mage/cards/i/IdrisSoulOfTheTARDIS.java new file mode 100644 index 00000000000..73f8bbe4dc3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/IdrisSoulOfTheTARDIS.java @@ -0,0 +1,167 @@ +package mage.cards.i; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileUntilSourceLeavesEffect; +import mage.abilities.keyword.VanishingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.game.ExileZone; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; + +import java.util.Collection; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * @author TheElk801 + */ +public final class IdrisSoulOfTheTARDIS extends CardImpl { + + public IdrisSoulOfTheTARDIS(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.INCARNATION); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Vanishing 3 + this.addAbility(new VanishingAbility(3)); + + // Imprint -- When Idris, Soul of the TARDIS enters the battlefield, exile another artifact you control until Idris leaves the battlefield. + this.addAbility(new EntersBattlefieldTriggeredAbility(new IdrisSoulOfTheTARDISExileEffect()).setAbilityWord(AbilityWord.IMPRINT)); + + // Idris has all activated and triggered abilities of the exiled card and gets +X/+X, where X is the exiled card's mana value. + this.addAbility(new SimpleStaticAbility(new IdrisSoulOfTheTARDISGainEffect())); + } + + private IdrisSoulOfTheTARDIS(final IdrisSoulOfTheTARDIS card) { + super(card); + } + + @Override + public IdrisSoulOfTheTARDIS copy() { + return new IdrisSoulOfTheTARDIS(this); + } +} + +class IdrisSoulOfTheTARDISExileEffect extends OneShotEffect { + + IdrisSoulOfTheTARDISExileEffect() { + super(Outcome.Benefit); + staticText = "exile another artifact you control until {this} leaves the battlefield"; + } + + private IdrisSoulOfTheTARDISExileEffect(final IdrisSoulOfTheTARDISExileEffect effect) { + super(effect); + } + + @Override + public IdrisSoulOfTheTARDISExileEffect copy() { + return new IdrisSoulOfTheTARDISExileEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null || !game.getBattlefield().contains( + StaticFilters.FILTER_CONTROLLED_ANOTHER_ARTIFACT, source, game, 1 + )) { + return false; + } + TargetPermanent target = new TargetPermanent(StaticFilters.FILTER_CONTROLLED_ANOTHER_ARTIFACT); + target.withNotTarget(true); + player.choose(outcome, target, source, game); + Permanent permanent = game.getPermanent(target.getFirstTarget()); + return permanent != null + && new ExileUntilSourceLeavesEffect() + .setTargetPointer(new FixedTarget(permanent, game)) + .apply(game, source); + } +} + +class IdrisSoulOfTheTARDISGainEffect extends ContinuousEffectImpl { + + IdrisSoulOfTheTARDISGainEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit); + staticText = "{this} has all activated and triggered abilities of the exiled card " + + "and gets +X/+X, where X is the exiled card's mana value"; + } + + private IdrisSoulOfTheTARDISGainEffect(final IdrisSoulOfTheTARDISGainEffect effect) { + super(effect); + } + + @Override + public IdrisSoulOfTheTARDISGainEffect copy() { + return new IdrisSoulOfTheTARDISGainEffect(this); + } + + @Override + public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + ExileZone exileZone = game.getExile().getExileZone(CardUtil.getExileZoneId( + game, source.getSourceId(), game.getState().getZoneChangeCounter(source.getSourceId()) + )); + if (permanent == null || exileZone == null || exileZone.isEmpty()) { + return false; + } + switch (layer) { + case AbilityAddingRemovingEffects_6: + Set abilities = exileZone + .getCards(game) + .stream() + .map(card -> card.getAbilities(game)) + .flatMap(Collection::stream) + .filter(ability -> ability.isActivatedAbility() || ability.isTriggeredAbility()) + .collect(Collectors.toSet()); + for (Ability ability : abilities) { + permanent.addAbility(ability, source.getSourceId(), game); + } + break; + case PTChangingEffects_7: + if (sublayer != SubLayer.ModifyPT_7c) { + break; + } + int boost = exileZone + .getCards(game) + .stream() + .mapToInt(MageObject::getManaValue) + .sum(); + permanent.addPower(boost); + permanent.addToughness(boost); + } + return true; + } + + @Override + public boolean apply(Game game, Ability source) { + return false; + } + + @Override + public boolean hasLayer(Layer layer) { + switch (layer) { + case AbilityAddingRemovingEffects_6: + case PTChangingEffects_7: + return true; + default: + return false; + } + } +} diff --git a/Mage.Sets/src/mage/cards/i/IfritWardenOfInferno.java b/Mage.Sets/src/mage/cards/i/IfritWardenOfInferno.java new file mode 100644 index 00000000000..f1de8a189dd --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/IfritWardenOfInferno.java @@ -0,0 +1,71 @@ +package mage.cards.i; + +import mage.MageInt; +import mage.Mana; +import mage.abilities.common.SagaAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.SourceHasCounterCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.ExileAndReturnSourceEffect; +import mage.abilities.effects.common.FightTargetSourceEffect; +import mage.abilities.effects.mana.BasicManaEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class IfritWardenOfInferno extends CardImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent("other target creature"); + private static final Condition condition = new SourceHasCounterCondition(CounterType.LORE, 3); + + public IfritWardenOfInferno(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, ""); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SAGA); + this.subtype.add(SubType.DEMON); + this.power = new MageInt(9); + this.toughness = new MageInt(9); + this.nightCard = true; + this.color.setRed(true); + + // (As this Saga enters and after your draw step, add a lore counter.) + SagaAbility sagaAbility = new SagaAbility(this); + + // I -- Lunge -- Ifrit fights up to one other target creature. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_I, ability -> { + ability.addEffect(new FightTargetSourceEffect()); + ability.addTarget(new TargetPermanent(0, 1, filter)); + ability.withFlavorWord("Lunge"); + }); + + // II, III -- Brimstone -- Add {R}{R}{R}{R}. If Ifrit has three or more lore counters on it, exile it, then return it to the battlefield + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_II, SagaChapter.CHAPTER_III, ability -> { + ability.addEffect(new BasicManaEffect(Mana.RedMana(5))); + ability.addEffect(new ConditionalOneShotEffect( + new ExileAndReturnSourceEffect(PutCards.BATTLEFIELD), condition, + "If {this} has three or more lore counters on it, exile it, then return it to the battlefield" + )); + ability.withFlavorWord("Brimstone"); + }); + this.addAbility(sagaAbility); + } + + private IfritWardenOfInferno(final IfritWardenOfInferno card) { + super(card); + } + + @Override + public IfritWardenOfInferno copy() { + return new IfritWardenOfInferno(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/IllicitAuction.java b/Mage.Sets/src/mage/cards/i/IllicitAuction.java index 454476ca0c8..55dc0df37d4 100644 --- a/Mage.Sets/src/mage/cards/i/IllicitAuction.java +++ b/Mage.Sets/src/mage/cards/i/IllicitAuction.java @@ -89,7 +89,7 @@ class IllicitAuctionEffect extends GainControlTargetEffect { newBid = Math.max(creatureValue % 2, computerLife - 100); } else { if (currentPlayer.canRespond()) { - newBid = currentPlayer.getAmount(highBid + 1, Integer.MAX_VALUE, "Choose bid", game); + newBid = currentPlayer.getAmount(highBid + 1, Integer.MAX_VALUE, "Choose bid", source, game); } } if (newBid > highBid) { diff --git a/Mage.Sets/src/mage/cards/i/IllusoryWrappings.java b/Mage.Sets/src/mage/cards/i/IllusoryWrappings.java index e39e7e9b80a..d8a125536e3 100644 --- a/Mage.Sets/src/mage/cards/i/IllusoryWrappings.java +++ b/Mage.Sets/src/mage/cards/i/IllusoryWrappings.java @@ -1,23 +1,21 @@ - package mage.cards.i; -import java.util.UUID; -import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.AttachEffect; -import mage.abilities.effects.common.continuous.SetBasePowerToughnessEnchantedEffect; +import mage.abilities.effects.common.continuous.SetBasePowerToughnessAttachedEffect; import mage.abilities.keyword.EnchantAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.constants.AttachmentType; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.Outcome; -import mage.constants.Zone; +import mage.constants.SubType; import mage.target.TargetPermanent; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + /** - * * @author Styxo */ public final class IllusoryWrappings extends CardImpl { @@ -31,12 +29,10 @@ public final class IllusoryWrappings extends CardImpl { TargetPermanent auraTarget = new TargetCreaturePermanent(); this.getSpellAbility().addTarget(auraTarget); this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); - Ability ability = new EnchantAbility(auraTarget); - this.addAbility(ability); + this.addAbility(new EnchantAbility(auraTarget)); // Enchanted creature has base power and toughness 0/2. - this.addAbility(new SimpleStaticAbility(new SetBasePowerToughnessEnchantedEffect())); - + this.addAbility(new SimpleStaticAbility(new SetBasePowerToughnessAttachedEffect(0, 2, AttachmentType.AURA))); } private IllusoryWrappings(final IllusoryWrappings card) { diff --git a/Mage.Sets/src/mage/cards/i/ImperialEdict.java b/Mage.Sets/src/mage/cards/i/ImperialEdict.java index 4749cf35661..284aea926bf 100644 --- a/Mage.Sets/src/mage/cards/i/ImperialEdict.java +++ b/Mage.Sets/src/mage/cards/i/ImperialEdict.java @@ -66,10 +66,7 @@ class ImperialEdictEffect extends OneShotEffect { FilterCreaturePermanent filter = new FilterCreaturePermanent("creature you control"); filter.add(new ControllerIdPredicate(player.getId())); Target target = new TargetPermanent(1, 1, filter, true); - if (target.canChoose(player.getId(), source, game)) { - while (!target.isChosen(game) && target.canChoose(player.getId(), source, game) && player.canRespond()) { - player.chooseTarget(Outcome.DestroyPermanent, target, source, game); - } + if (player.chooseTarget(Outcome.DestroyPermanent, target, source, game)) { Permanent permanent = game.getPermanent(target.getFirstTarget()); if (permanent != null) { permanent.destroy(source, game, false); diff --git a/Mage.Sets/src/mage/cards/i/IncarnationTechnique.java b/Mage.Sets/src/mage/cards/i/IncarnationTechnique.java index 25aec2b8e8e..85526cdbb27 100644 --- a/Mage.Sets/src/mage/cards/i/IncarnationTechnique.java +++ b/Mage.Sets/src/mage/cards/i/IncarnationTechnique.java @@ -2,6 +2,7 @@ package mage.cards.i; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.MillCardsControllerEffect; import mage.abilities.keyword.DemonstrateAbility; import mage.cards.Card; import mage.cards.CardImpl; @@ -29,6 +30,7 @@ public final class IncarnationTechnique extends CardImpl { this.addAbility(new DemonstrateAbility()); // Mill five cards, then return a creature card from your graveyard to the battlefield. + this.getSpellAbility().addEffect(new MillCardsControllerEffect(5).concatBy(", then")); this.getSpellAbility().addEffect(new IncarnationTechniqueEffect()); } @@ -46,7 +48,7 @@ class IncarnationTechniqueEffect extends OneShotEffect { IncarnationTechniqueEffect() { super(Outcome.Benefit); - staticText = "mill five cards, then return a creature card from your graveyard to the battlefield"; + staticText = "return a creature card from your graveyard to the battlefield"; } private IncarnationTechniqueEffect(final IncarnationTechniqueEffect effect) { @@ -64,7 +66,6 @@ class IncarnationTechniqueEffect extends OneShotEffect { if (player == null) { return false; } - player.millCards(5, source, game); TargetCard target = new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_CREATURE_YOUR_GRAVEYARD); target.withNotTarget(true); if (!target.canChoose(source.getControllerId(), source, game)) { diff --git a/Mage.Sets/src/mage/cards/i/IncineratorOfTheGuilty.java b/Mage.Sets/src/mage/cards/i/IncineratorOfTheGuilty.java index da0c6f4a502..2c9e23f4abb 100644 --- a/Mage.Sets/src/mage/cards/i/IncineratorOfTheGuilty.java +++ b/Mage.Sets/src/mage/cards/i/IncineratorOfTheGuilty.java @@ -88,7 +88,7 @@ class IncineratorOfTheGuiltyEffect extends OneShotEffect { return false; } - int xValue = controller.announceXMana(0, Integer.MAX_VALUE, "Announce the value for X", game, source); + int xValue = controller.announceX(0, Integer.MAX_VALUE, "Announce the value for {X} (collect evidence)", game, source, false); CollectEvidenceCost cost = new CollectEvidenceCost(xValue); if (!cost.pay(source, game, source, source.getControllerId(), false, null)) { return false; diff --git a/Mage.Sets/src/mage/cards/i/Incriminate.java b/Mage.Sets/src/mage/cards/i/Incriminate.java index ef257e29f49..32eb1daa2b7 100644 --- a/Mage.Sets/src/mage/cards/i/Incriminate.java +++ b/Mage.Sets/src/mage/cards/i/Incriminate.java @@ -1,25 +1,25 @@ package mage.cards.i; -import java.util.ArrayList; -import java.util.List; 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.StaticFilters; import mage.filter.predicate.Predicates; +import mage.filter.predicate.permanent.PermanentIdPredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; -import mage.target.common.TargetCreaturePermanentSameController; - -import java.util.UUID; -import mage.filter.FilterPermanent; -import mage.filter.predicate.permanent.PermanentIdPredicate; -import mage.target.TargetPermanent; +import mage.target.common.TargetPermanentSameController; import mage.target.common.TargetSacrifice; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + /** * @author TheElk801 */ @@ -30,7 +30,7 @@ public final class Incriminate extends CardImpl { // Choose two target creatures controlled by the same player. That player sacrifices one of them. this.getSpellAbility().addEffect(new IncriminateEffect()); - this.getSpellAbility().addTarget(new TargetCreaturePermanentSameController(2)); + this.getSpellAbility().addTarget(new TargetPermanentSameController(StaticFilters.FILTER_PERMANENT_CREATURES)); } private Incriminate(final Incriminate card) { diff --git a/Mage.Sets/src/mage/cards/i/InstantRamen.java b/Mage.Sets/src/mage/cards/i/InstantRamen.java new file mode 100644 index 00000000000..ddcece335cb --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InstantRamen.java @@ -0,0 +1,42 @@ +package mage.cards.i; + +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.keyword.FlashAbility; +import mage.abilities.token.FoodAbility; +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 InstantRamen extends CardImpl { + + public InstantRamen(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); + + this.subtype.add(SubType.FOOD); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // When this artifact enters, draw a card. + this.addAbility(new EntersBattlefieldTriggeredAbility(new DrawCardSourceControllerEffect(1))); + + // {2}, {T}, Sacrifice this artifact: You gain 3 life. + this.addAbility(new FoodAbility()); + } + + private InstantRamen(final InstantRamen card) { + super(card); + } + + @Override + public InstantRamen copy() { + return new InstantRamen(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/InterceptorShadowsHound.java b/Mage.Sets/src/mage/cards/i/InterceptorShadowsHound.java new file mode 100644 index 00000000000..d585ea6645d --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InterceptorShadowsHound.java @@ -0,0 +1,61 @@ +package mage.cards.i; + +import mage.MageInt; +import mage.abilities.common.AttacksWithCreaturesTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.ReturnSourceFromGraveyardToBattlefieldEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class InterceptorShadowsHound extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent(SubType.ASSASSIN, "Assassins"); + + public InterceptorShadowsHound(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.DOG); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // Menace + this.addAbility(new MenaceAbility()); + + // Assassins you control have menace. + this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect( + new MenaceAbility(false), Duration.WhileControlled, filter + ))); + + // Whenever you attack with one or more legendary creatures, you may pay {2}{B}. If you do, return this card from your graveyard to the battlefield tapped and attacking. + this.addAbility(new AttacksWithCreaturesTriggeredAbility( + Zone.GRAVEYARD, + new DoIfCostPaid( + new ReturnSourceFromGraveyardToBattlefieldEffect( + true, false, false, true + ), new ManaCostsImpl<>("{2}{B}") + ), 1, StaticFilters.FILTER_CREATURES_LEGENDARY + )); + } + + private InterceptorShadowsHound(final InterceptorShadowsHound card) { + super(card); + } + + @Override + public InterceptorShadowsHound copy() { + return new InterceptorShadowsHound(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/IntoThePit.java b/Mage.Sets/src/mage/cards/i/IntoThePit.java new file mode 100644 index 00000000000..b0529908c51 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/IntoThePit.java @@ -0,0 +1,101 @@ +package mage.cards.i; + +import mage.MageIdentifier; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.Costs; +import mage.abilities.costs.CostsImpl; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; +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.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class IntoThePit extends CardImpl { + + public IntoThePit(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{B}"); + + // You may look at the top card of your library any time. + this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); + + // You may cast spells from the top of your library by sacrificing a nonland permanent in addition to paying their other costs. + this.addAbility(new SimpleStaticAbility(new IntoThePitEffect()) + .setIdentifier(MageIdentifier.IntoThePitAlternateCast)); + } + + private IntoThePit(final IntoThePit card) { + super(card); + } + + @Override + public IntoThePit copy() { + return new IntoThePit(this); + } +} + +class IntoThePitEffect extends AsThoughEffectImpl { + + IntoThePitEffect() { + super(AsThoughEffectType.CAST_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.AIDontUseIt); + staticText = "you may cast spells from the top of your library by sacrificing " + + "a nonland permanent in addition to paying their other costs"; + } + + private IntoThePitEffect(final IntoThePitEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public IntoThePitEffect copy() { + return new IntoThePitEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + Card cardToCheck = game.getCard(objectId); + if (cardToCheck == null + || !source.isControlledBy(affectedControllerId) + || !cardToCheck.isOwnedBy(affectedControllerId)) { + return false; + } + Player player = game.getPlayer(cardToCheck.getOwnerId()); + if (player == null) { + return false; + } + Card topCard = player.getLibrary().getFromTop(game); + if (topCard == null + || !topCard.getId().equals(cardToCheck.getMainCard().getId()) + || cardToCheck.isLand(game)) { + return false; + } + + Costs newCosts = new CostsImpl<>(); + newCosts.add(new SacrificeTargetCost(StaticFilters.FILTER_PERMANENT_NON_LAND)); + newCosts.addAll(cardToCheck.getSpellAbility().getCosts()); + player.setCastSourceIdWithAlternateMana( + cardToCheck.getId(), cardToCheck.getManaCost(), newCosts, + MageIdentifier.IntoThePitAlternateCast + ); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/i/IsarethTheAwakener.java b/Mage.Sets/src/mage/cards/i/IsarethTheAwakener.java index 95b2b0815dc..4b3c0afda9c 100644 --- a/Mage.Sets/src/mage/cards/i/IsarethTheAwakener.java +++ b/Mage.Sets/src/mage/cards/i/IsarethTheAwakener.java @@ -85,7 +85,7 @@ class IsarethTheAwakenerCreateReflexiveTriggerEffect extends OneShotEffect { || !player.chooseUse(Outcome.BoostCreature, "Pay " + cost.getText() + "?", source, game)) { return false; } - int costX = player.announceXMana(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source); + int costX = player.announceX(0, Integer.MAX_VALUE, "Announce the value for {X} (pay to return due mana value)", game, source, true); cost.add(new GenericManaCost(costX)); if (!cost.pay(source, game, source, source.getControllerId(), false, null)) { return false; diff --git a/Mage.Sets/src/mage/cards/i/IshgardTheHolySee.java b/Mage.Sets/src/mage/cards/i/IshgardTheHolySee.java new file mode 100644 index 00000000000..59c720a4453 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/IshgardTheHolySee.java @@ -0,0 +1,49 @@ +package mage.cards.i; + +import mage.abilities.common.EntersBattlefieldTappedAbility; +import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect; +import mage.abilities.mana.WhiteManaAbility; +import mage.cards.AdventureCard; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.common.FilterArtifactOrEnchantmentCard; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class IshgardTheHolySee extends AdventureCard { + + private static final FilterCard filter = new FilterArtifactOrEnchantmentCard("artifact and/or enchantment cards from your graveyard"); + + public IshgardTheHolySee(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, new CardType[]{CardType.SORCERY}, "", "Faith & Grief", "{3}{W}{W}"); + + this.subtype.add(SubType.TOWN); + + // This land enters tapped. + this.addAbility(new EntersBattlefieldTappedAbility()); + + // {T}: Add {W}. + this.addAbility(new WhiteManaAbility()); + + // Faith & Grief + // Return up to two target artifact and/or enchantment cards from your graveyard to your hand. + this.getSpellCard().getSpellAbility().addEffect(new ReturnFromGraveyardToHandTargetEffect()); + this.getSpellCard().getSpellAbility().addTarget(new TargetCardInYourGraveyard(0, 2, filter)); + this.finalizeAdventure(); + } + + private IshgardTheHolySee(final IshgardTheHolySee card) { + super(card); + } + + @Override + public IshgardTheHolySee copy() { + return new IshgardTheHolySee(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/ItemShopkeep.java b/Mage.Sets/src/mage/cards/i/ItemShopkeep.java new file mode 100644 index 00000000000..d75e7b04767 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/ItemShopkeep.java @@ -0,0 +1,54 @@ +package mage.cards.i; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksWithCreaturesTriggeredAbility; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterAttackingCreature; +import mage.filter.predicate.permanent.EquippedPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ItemShopkeep extends CardImpl { + + private static final FilterPermanent filter = new FilterAttackingCreature("attacking equipped creature"); + + static { + filter.add(EquippedPredicate.instance); + } + + public ItemShopkeep(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.CITIZEN); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Whenever you attack, target attacking equipped creature gains menace until end of turn. + Ability ability = new AttacksWithCreaturesTriggeredAbility( + new GainAbilityTargetEffect(new MenaceAbility(false)), 1 + ); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + } + + private ItemShopkeep(final ItemShopkeep card) { + super(card); + } + + @Override + public ItemShopkeep copy() { + return new ItemShopkeep(this); + } +} diff --git a/Mage.Sets/src/mage/cards/j/JacobFrye.java b/Mage.Sets/src/mage/cards/j/JacobFrye.java new file mode 100644 index 00000000000..b5d47e28708 --- /dev/null +++ b/Mage.Sets/src/mage/cards/j/JacobFrye.java @@ -0,0 +1,103 @@ +package mage.cards.j; + +import mage.ApprovingObject; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.FreerunningAbility; +import mage.abilities.keyword.PartnerWithAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class JacobFrye extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(SubType.ASSASSIN, "Assassins you control"); + private static final FilterCard filter2 = new FilterCard("Assassin card or card with freerunning"); + + static { + filter.add(TargetController.YOU.getControllerPredicate()); + filter2.add(Predicates.or( + SubType.ASSASSIN.getPredicate(), + new AbilityPredicate(FreerunningAbility.class) + )); + } + + public JacobFrye(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ASSASSIN); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Partner with Evie Frye + this.addAbility(new PartnerWithAbility("Evie Frye")); + + // Whenever one or more Assassins you control deal combat damage to a player, exile up to one target Assassin card or card with freerunning from your graveyard. If you do, copy it. You may cast the copy. + Ability ability = new OneOrMoreCombatDamagePlayerTriggeredAbility(new JacobFryeEffect(), filter); + ability.addTarget(new TargetCardInYourGraveyard(0, 1, filter2)); + this.addAbility(ability); + } + + private JacobFrye(final JacobFrye card) { + super(card); + } + + @Override + public JacobFrye copy() { + return new JacobFrye(this); + } +} + +class JacobFryeEffect extends OneShotEffect { + + JacobFryeEffect() { + super(Outcome.Benefit); + staticText = "exile up to one target Assassin card or card with freerunning " + + "from your graveyard. If you do, copy it. You may cast the copy"; + } + + private JacobFryeEffect(final JacobFryeEffect effect) { + super(effect); + } + + @Override + public JacobFryeEffect copy() { + return new JacobFryeEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); + if (player == null || card == null) { + return false; + } + player.moveCards(card, Zone.EXILED, source, game); + Card copiedCard = game.copyCard(card, source, source.getControllerId()); + copiedCard.setZone(Zone.OUTSIDE, game); + game.getState().setValue("PlayFromNotOwnHandZone" + copiedCard.getId(), Boolean.TRUE); + player.cast( + player.chooseAbilityForCast(copiedCard, game, false), + game, false, new ApprovingObject(source, game) + ); + game.getState().setValue("PlayFromNotOwnHandZone" + copiedCard.getId(), null); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/j/JaradGolgariLichLord.java b/Mage.Sets/src/mage/cards/j/JaradGolgariLichLord.java index 9da62d017c8..27c3912af93 100644 --- a/Mage.Sets/src/mage/cards/j/JaradGolgariLichLord.java +++ b/Mage.Sets/src/mage/cards/j/JaradGolgariLichLord.java @@ -1,8 +1,8 @@ package mage.cards.j; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; +import mage.abilities.assignment.common.SubTypeAssignment; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.common.SacrificeTargetCost; @@ -13,27 +13,30 @@ import mage.abilities.dynamicvalue.common.SacrificeCostCreaturesPower; import mage.abilities.effects.common.LoseLifeOpponentsEffect; import mage.abilities.effects.common.ReturnSourceFromGraveyardToHandEffect; import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; +import mage.filter.FilterPermanent; import mage.filter.StaticFilters; -import mage.filter.common.FilterControlledPermanent; -import mage.target.common.TargetControlledCreaturePermanent; -import mage.target.common.TargetControlledPermanent; +import mage.filter.predicate.Predicates; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetSacrifice; + +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; /** - * * @author LevelX2 */ public final class JaradGolgariLichLord extends CardImpl { - private static final FilterControlledPermanent filterSwamp = new FilterControlledPermanent("a Swamp"); - private static final FilterControlledPermanent filterForest = new FilterControlledPermanent("a Forest"); - - static { - filterSwamp.add(SubType.SWAMP.getPredicate()); - filterForest.add(SubType.FOREST.getPredicate()); - } + private static final DynamicValue xValue = new CardsInControllerGraveyardCount(StaticFilters.FILTER_CARD_CREATURE); + private static final Hint hint = new ValueHint("Creatures in your graveyard", xValue); public JaradGolgariLichLord(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{B}{B}{G}{G}"); @@ -45,20 +48,20 @@ public final class JaradGolgariLichLord extends CardImpl { this.toughness = new MageInt(2); // Jarad, Golgari Lich Lord gets +1/+1 for each creature card in your graveyard. - DynamicValue amount = new CardsInControllerGraveyardCount(StaticFilters.FILTER_CARD_CREATURE); - Ability ability = new SimpleStaticAbility(new BoostSourceEffect(amount, amount, Duration.WhileOnBattlefield)); - this.addAbility(ability); + this.addAbility(new SimpleStaticAbility(new BoostSourceEffect(xValue, xValue, Duration.WhileOnBattlefield)).addHint(hint)); // {1}{B}{G}, Sacrifice another creature: Each opponent loses life equal to the sacrificed creature's power. - ability = new SimpleActivatedAbility(new LoseLifeOpponentsEffect(SacrificeCostCreaturesPower.instance), new ManaCostsImpl<>("{1}{B}{G}")); + Ability ability = new SimpleActivatedAbility(new LoseLifeOpponentsEffect(SacrificeCostCreaturesPower.instance) + .setText("each opponent loses life equal to the sacrificed creature's power"), new ManaCostsImpl<>("{1}{B}{G}")); ability.addCost(new SacrificeTargetCost(StaticFilters.FILTER_CONTROLLED_ANOTHER_CREATURE)); this.addAbility(ability); // Sacrifice a Swamp and a Forest: Return Jarad from your graveyard to your hand. - ability = new SimpleActivatedAbility(Zone.GRAVEYARD, new ReturnSourceFromGraveyardToHandEffect(), - new SacrificeTargetCost(filterSwamp)); - ability.addCost(new SacrificeTargetCost(filterForest)); - this.addAbility(ability); + this.addAbility(new SimpleActivatedAbility( + Zone.GRAVEYARD, + new ReturnSourceFromGraveyardToHandEffect(), + new SacrificeTargetCost(new JaradGolgariLichLordTarget()) + )); } @@ -71,3 +74,48 @@ public final class JaradGolgariLichLord extends CardImpl { return new JaradGolgariLichLord(this); } } + +class JaradGolgariLichLordTarget extends TargetSacrifice { + + private static final FilterPermanent filter = new FilterPermanent("a Swamp and a Forest"); + + static { + filter.add(Predicates.or( + SubType.SWAMP.getPredicate(), + SubType.FOREST.getPredicate() + )); + } + + private static final SubTypeAssignment subtypeAssigner = new SubTypeAssignment(SubType.SWAMP, SubType.FOREST); + + JaradGolgariLichLordTarget() { + super(2, 2, filter); + } + + private JaradGolgariLichLordTarget(final JaradGolgariLichLordTarget target) { + super(target); + } + + @Override + public JaradGolgariLichLordTarget copy() { + return new JaradGolgariLichLordTarget(this); + } + + @Override + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (!super.canTarget(playerId, id, source, game)) { + return false; + } + if (this.getTargets().isEmpty()) { + return true; + } + Set permanents = this + .getTargets() + .stream() + .map(game::getPermanent) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + permanents.add(game.getPermanent(id)); + return subtypeAssigner.getRoleCount(permanents, game) >= permanents.size(); + } +} diff --git a/Mage.Sets/src/mage/cards/j/JechtReluctantGuardian.java b/Mage.Sets/src/mage/cards/j/JechtReluctantGuardian.java new file mode 100644 index 00000000000..5cb0bcea09c --- /dev/null +++ b/Mage.Sets/src/mage/cards/j/JechtReluctantGuardian.java @@ -0,0 +1,51 @@ +package mage.cards.j; + +import mage.MageInt; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.effects.common.ExileAndReturnSourceEffect; +import mage.abilities.keyword.MenaceAbility; +import mage.abilities.keyword.TransformAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.PutCards; +import mage.constants.SubType; +import mage.constants.SuperType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class JechtReluctantGuardian extends CardImpl { + + public JechtReluctantGuardian(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + this.secondSideCardClazz = mage.cards.b.BraskasFinalAeon.class; + + // Menace + this.addAbility(new MenaceAbility()); + + // Whenever Jecht deals combat damage to a player, you may exile it, then return it to the battlefield transformed under its owner's control. + this.addAbility(new TransformAbility()); + this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility( + new ExileAndReturnSourceEffect(PutCards.BATTLEFIELD_TRANSFORMED) + .setText("exile it, then return it to the battlefield transformed under its owner's control"), true + )); + } + + private JechtReluctantGuardian(final JechtReluctantGuardian card) { + super(card); + } + + @Override + public JechtReluctantGuardian copy() { + return new JechtReluctantGuardian(this); + } +} diff --git a/Mage.Sets/src/mage/cards/j/JhoiraOfTheGhitu.java b/Mage.Sets/src/mage/cards/j/JhoiraOfTheGhitu.java index 5f81e40efde..44716f7ae23 100644 --- a/Mage.Sets/src/mage/cards/j/JhoiraOfTheGhitu.java +++ b/Mage.Sets/src/mage/cards/j/JhoiraOfTheGhitu.java @@ -1,27 +1,25 @@ package mage.cards.j; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; import mage.MageInt; -import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; -import mage.abilities.costs.Cost; import mage.abilities.costs.common.ExileFromHandCost; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.continuous.GainSuspendEffect; import mage.abilities.keyword.SuspendAbility; -import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; -import mage.counters.CounterType; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.SuperType; import mage.filter.common.FilterNonlandCard; import mage.game.Game; -import mage.players.Player; import mage.target.common.TargetCardInHand; +import mage.util.CardUtil; + +import java.util.Collection; +import java.util.UUID; /** * @author LevelX2 @@ -41,7 +39,6 @@ public final class JhoiraOfTheGhitu extends CardImpl { Ability ability = new SimpleActivatedAbility(new JhoiraOfTheGhituSuspendEffect(), new GenericManaCost(2)); ability.addCost(new ExileFromHandCost(new TargetCardInHand(new FilterNonlandCard("a nonland card from your hand")))); this.addAbility(ability); - } private JhoiraOfTheGhitu(final JhoiraOfTheGhitu card) { @@ -58,7 +55,9 @@ class JhoiraOfTheGhituSuspendEffect extends OneShotEffect { JhoiraOfTheGhituSuspendEffect() { super(Outcome.PutCardInPlay); - this.staticText = "Put four time counters on the exiled card. If it doesn't have suspend, it gains suspend. (At the beginning of your upkeep, remove a time counter from that card. When the last is removed, cast it without paying its mana cost. If it's a creature, it has haste.)"; + this.staticText = "Put four time counters on the exiled card. If it doesn't have suspend, " + + "it gains suspend. (At the beginning of your upkeep, remove a time counter from that card. " + + "When the last is removed, cast it without paying its mana cost. If it's a creature, it has haste.)"; } private JhoiraOfTheGhituSuspendEffect(final JhoiraOfTheGhituSuspendEffect effect) { @@ -72,36 +71,13 @@ class JhoiraOfTheGhituSuspendEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller == null) { - return false; - } - List cards = new ArrayList<>(); - for (Cost cost : source.getCosts()) { - if (cost instanceof ExileFromHandCost) { - cards = ((ExileFromHandCost) cost).getCards(); - } - } - if (cards != null && !cards.isEmpty()) { - // always one card to exile - Card card = game.getCard(cards.get(0).getId()); - if (card == null) { - return false; - } - card = card.getMainCard(); - - boolean hasSuspend = card.getAbilities(game).containsClass(SuspendAbility.class); - - UUID exileId = SuspendAbility.getSuspendExileId(controller.getId(), game); - if (controller.moveCardToExileWithInfo(card, exileId, "Suspended cards of " + controller.getName(), source, game, Zone.HAND, true)) { - card.addCounters(CounterType.TIME.createInstance(4), source.getControllerId(), source, game); - if (!hasSuspend) { - game.addEffect(new GainSuspendEffect(new MageObjectReference(card, game)), source); - } - game.informPlayers(controller.getLogName() + " suspends 4 - " + card.getName()); - return true; - } - } - return false; + return SuspendAbility.addTimeCountersAndSuspend( + CardUtil.castStream(source.getCosts(), ExileFromHandCost.class) + .map(ExileFromHandCost::getCards) + .flatMap(Collection::stream) + .findFirst() + .orElse(null), + 4, source, game + ); } } diff --git a/Mage.Sets/src/mage/cards/j/JidoorAristocraticCapital.java b/Mage.Sets/src/mage/cards/j/JidoorAristocraticCapital.java new file mode 100644 index 00000000000..21a02d328c0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/j/JidoorAristocraticCapital.java @@ -0,0 +1,45 @@ +package mage.cards.j; + +import mage.abilities.common.EntersBattlefieldTappedAbility; +import mage.abilities.effects.common.MillHalfLibraryTargetEffect; +import mage.abilities.mana.BlueManaAbility; +import mage.cards.AdventureCard; +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 JidoorAristocraticCapital extends AdventureCard { + + public JidoorAristocraticCapital(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, new CardType[]{CardType.SORCERY}, "", "Overture", "{4}{U}{U}"); + + this.subtype.add(SubType.TOWN); + + // This land enters tapped. + this.addAbility(new EntersBattlefieldTappedAbility()); + + // {T}: Add {U}. + this.addAbility(new BlueManaAbility()); + + // Overture + // Target opponent mills half their library, rounded down. + this.getSpellCard().getSpellAbility().addEffect(new MillHalfLibraryTargetEffect(false)); + this.getSpellCard().getSpellAbility().addTarget(new TargetOpponent()); + this.finalizeAdventure(); + } + + private JidoorAristocraticCapital(final JidoorAristocraticCapital card) { + super(card); + } + + @Override + public JidoorAristocraticCapital copy() { + return new JidoorAristocraticCapital(this); + } +} diff --git a/Mage.Sets/src/mage/cards/j/JillShivasDominant.java b/Mage.Sets/src/mage/cards/j/JillShivasDominant.java new file mode 100644 index 00000000000..c115eba3121 --- /dev/null +++ b/Mage.Sets/src/mage/cards/j/JillShivasDominant.java @@ -0,0 +1,69 @@ +package mage.cards.j; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.ExileAndReturnSourceEffect; +import mage.abilities.effects.common.ReturnToHandTargetEffect; +import mage.abilities.keyword.TransformAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.PutCards; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterNonlandPermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class JillShivasDominant extends CardImpl { + + private static final FilterPermanent filter = new FilterNonlandPermanent("other target nonland permanent"); + + static { + filter.add(AnotherPredicate.instance); + } + + public JillShivasDominant(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.NOBLE); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + this.secondSideCardClazz = mage.cards.s.ShivaWardenOfIce.class; + + // When Jill enters, return up to one other target nonland permanent to its owner's hand. + Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnToHandTargetEffect()); + ability.addTarget(new TargetPermanent(0, 1, filter)); + this.addAbility(ability); + + // {3}{U}{U}, {T}: Exile Jill, then return it to the battlefield transformed under its owner's control. Activate only as a sorcery. + this.addAbility(new TransformAbility()); + ability = new ActivateAsSorceryActivatedAbility( + new ExileAndReturnSourceEffect(PutCards.BATTLEFIELD_TRANSFORMED), new ManaCostsImpl<>("{3}{U}{U}") + ); + ability.addCost(new TapSourceCost()); + this.addAbility(ability); + } + + private JillShivasDominant(final JillShivasDominant card) { + super(card); + } + + @Override + public JillShivasDominant copy() { + return new JillShivasDominant(this); + } +} diff --git a/Mage.Sets/src/mage/cards/j/JudgmentBolt.java b/Mage.Sets/src/mage/cards/j/JudgmentBolt.java new file mode 100644 index 00000000000..d0581aa5d85 --- /dev/null +++ b/Mage.Sets/src/mage/cards/j/JudgmentBolt.java @@ -0,0 +1,45 @@ +package mage.cards.j; + +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.DamageTargetControllerEffect; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.common.FilterControlledPermanent; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class JudgmentBolt extends CardImpl { + + private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(new FilterControlledPermanent(SubType.EQUIPMENT)); + private static final Hint hint = new ValueHint("Equipment you control", xValue); + + public JudgmentBolt(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{R}"); + + // Judgment Bolt deals 5 damage to target creature and X damage to that creature's controller, where X is the number of Equipment you control. + this.getSpellAbility().addEffect(new DamageTargetEffect(5)); + this.getSpellAbility().addEffect(new DamageTargetControllerEffect(xValue) + .setText("and X damage to that creature's controller, where X is the number of Equipment you control")); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + this.getSpellAbility().addHint(hint); + } + + private JudgmentBolt(final JudgmentBolt card) { + super(card); + } + + @Override + public JudgmentBolt copy() { + return new JudgmentBolt(this); + } +} diff --git a/Mage.Sets/src/mage/cards/k/KainTraitorousDragoon.java b/Mage.Sets/src/mage/cards/k/KainTraitorousDragoon.java new file mode 100644 index 00000000000..e11d5e5f309 --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KainTraitorousDragoon.java @@ -0,0 +1,109 @@ +package mage.cards.k; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.MyTurnCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.effects.common.continuous.GainControlTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Controllable; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.TreasureToken; +import mage.players.Player; +import mage.target.targetpointer.FixedTarget; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class KainTraitorousDragoon extends CardImpl { + + public KainTraitorousDragoon(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.KNIGHT); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // Jump -- During your turn, Kain has flying. + this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( + new GainAbilitySourceEffect(FlyingAbility.getInstance()), + MyTurnCondition.instance, "during your turn, {this} has flying" + )).withFlavorWord("Jump")); + + // Whenever Kain deals combat damage to a player, that player gains control of Kain. If they do, you draw that many cards, create that many tapped Treasure tokens, then lose that much life. + this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility( + new KainTraitorousDragoonEffect(), false, true + )); + } + + private KainTraitorousDragoon(final KainTraitorousDragoon card) { + super(card); + } + + @Override + public KainTraitorousDragoon copy() { + return new KainTraitorousDragoon(this); + } +} + +class KainTraitorousDragoonEffect extends OneShotEffect { + + KainTraitorousDragoonEffect() { + super(Outcome.Benefit); + staticText = ", that player gains control of {this}. If they do, you draw that many cards, " + + "create that many tapped Treasure tokens, then lose that much life"; + } + + private KainTraitorousDragoonEffect(final KainTraitorousDragoonEffect effect) { + super(effect); + } + + @Override + public KainTraitorousDragoonEffect copy() { + return new KainTraitorousDragoonEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + if (player == null || permanent == null) { + return false; + } + game.addEffect(new GainControlTargetEffect( + Duration.Custom, true, player.getId() + ).setTargetPointer(new FixedTarget(permanent, game)), source); + game.processAction(); + if (!permanent.isControlledBy(player.getId())) { + return false; + } + int amount = (Integer) getValue("damage"); + return amount > 0 + && Optional + .ofNullable(source) + .map(Controllable::getControllerId) + .map(game::getPlayer) + .map(controller -> { + controller.drawCards(amount, source, game); + new TreasureToken().putOntoBattlefield( + amount, game, source, controller.getId(), true, false + ); + controller.loseLife(amount, game, source, false); + return true; + }) + .orElse(false); + } +} diff --git a/Mage.Sets/src/mage/cards/k/KarplusanMinotaur.java b/Mage.Sets/src/mage/cards/k/KarplusanMinotaur.java index 36ffdbc9158..f99c473102d 100644 --- a/Mage.Sets/src/mage/cards/k/KarplusanMinotaur.java +++ b/Mage.Sets/src/mage/cards/k/KarplusanMinotaur.java @@ -1,9 +1,9 @@ - package mage.cards.k; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.WonCoinFlipControllerTriggeredAbility; import mage.abilities.costs.Cost; import mage.abilities.costs.CostImpl; import mage.abilities.effects.common.DamageTargetEffect; @@ -41,7 +41,9 @@ public final class KarplusanMinotaur extends CardImpl { this.addAbility(new CumulativeUpkeepAbility(new KarplusanMinotaurCost())); // Whenever you win a coin flip, Karplusan Minotaur deals 1 damage to any target. - this.addAbility(new KarplusanMinotaurFlipWinTriggeredAbility()); + Ability ability = new WonCoinFlipControllerTriggeredAbility(new DamageTargetEffect(1)); + ability.addTarget(new TargetAnyTarget()); + this.addAbility(ability); // Whenever you lose a coin flip, Karplusan Minotaur deals 1 damage to any target of an opponent's choice. this.addAbility(new KarplusanMinotaurFlipLoseTriggeredAbility()); @@ -57,41 +59,6 @@ public final class KarplusanMinotaur extends CardImpl { } } -class KarplusanMinotaurFlipWinTriggeredAbility extends TriggeredAbilityImpl { - - public KarplusanMinotaurFlipWinTriggeredAbility() { - super(Zone.BATTLEFIELD, new DamageTargetEffect(1), false); - this.addTarget(new TargetAnyTarget()); - } - - private KarplusanMinotaurFlipWinTriggeredAbility(final KarplusanMinotaurFlipWinTriggeredAbility ability) { - super(ability); - } - - @Override - public KarplusanMinotaurFlipWinTriggeredAbility copy() { - return new KarplusanMinotaurFlipWinTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.COIN_FLIPPED; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - CoinFlippedEvent flipEvent = (CoinFlippedEvent) event; - return flipEvent.getPlayerId().equals(controllerId) - && flipEvent.isWinnable() - && (flipEvent.getChosen() == flipEvent.getResult()); - } - - @Override - public String getRule() { - return "Whenever you win a coin flip, {this} deals 1 damage to any target."; - } -} - class KarplusanMinotaurFlipLoseTriggeredAbility extends TriggeredAbilityImpl { public KarplusanMinotaurFlipLoseTriggeredAbility() { @@ -117,9 +84,7 @@ class KarplusanMinotaurFlipLoseTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { CoinFlippedEvent flipEvent = (CoinFlippedEvent) event; - return flipEvent.getPlayerId().equals(controllerId) - && flipEvent.isWinnable() - && (flipEvent.getChosen() != flipEvent.getResult()); + return isControlledBy(event.getPlayerId()) && flipEvent.isWinnable() && !flipEvent.wasWon(); } @Override diff --git a/Mage.Sets/src/mage/cards/k/KellanPlanarTrailblazer.java b/Mage.Sets/src/mage/cards/k/KellanPlanarTrailblazer.java index 8f8aff283d0..d1529781ee9 100644 --- a/Mage.Sets/src/mage/cards/k/KellanPlanarTrailblazer.java +++ b/Mage.Sets/src/mage/cards/k/KellanPlanarTrailblazer.java @@ -61,7 +61,7 @@ class KellanPlanarTrailblazerDetectiveEffect extends OneShotEffect { KellanPlanarTrailblazerDetectiveEffect() { super(Outcome.Benefit); staticText = "if {this} is a Scout, it becomes a Human Faerie Detective and gains " - + "\"Whenever this creature deals combat damage to a player, exile the top card of your library. " + + "\"Whenever {this} deals combat damage to a player, exile the top card of your library. " + "You may play that card this turn.\""; } @@ -125,4 +125,4 @@ class KellanPlanarTrailblazerRogueEffect extends OneShotEffect { ), source); return true; } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/k/KeysToTheHouse.java b/Mage.Sets/src/mage/cards/k/KeysToTheHouse.java new file mode 100644 index 00000000000..2f7522dfebc --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KeysToTheHouse.java @@ -0,0 +1,57 @@ +package mage.cards.k; + +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.LockOrUnlockRoomTargetEffect; +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.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledPermanent; +import mage.target.TargetPermanent; +import mage.target.common.TargetCardInLibrary; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class KeysToTheHouse extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledPermanent(SubType.ROOM); + + public KeysToTheHouse(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}"); + + // {1}, {T}, Sacrifice Keys to the House: Search your library for a basic land card, reveal it, put it into your hand, then shuffle. + Ability ability = new SimpleActivatedAbility(new SearchLibraryPutInHandEffect( + new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_LAND), true + ), new GenericManaCost(1)); + ability.addCost(new TapSourceCost()); + ability.addCost(new SacrificeSourceCost()); + this.addAbility(ability); + + // {3}, {T}, Sacrifice Keys to the House: Lock or unlock a door of target Room you control. Activate only as a sorcery. + ability = new ActivateAsSorceryActivatedAbility(new LockOrUnlockRoomTargetEffect(), new GenericManaCost(3)); + ability.addCost(new TapSourceCost()); + ability.addCost(new SacrificeSourceCost()); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + } + + private KeysToTheHouse(final KeysToTheHouse card) { + super(card); + } + + @Override + public KeysToTheHouse copy() { + return new KeysToTheHouse(this); + } +} diff --git a/Mage.Sets/src/mage/cards/k/KimahriValiantGuardian.java b/Mage.Sets/src/mage/cards/k/KimahriValiantGuardian.java new file mode 100644 index 00000000000..321f260aede --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KimahriValiantGuardian.java @@ -0,0 +1,103 @@ +package mage.cards.k; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.TapTargetEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.VigilanceAbility; +import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetOpponentsCreaturePermanent; +import mage.util.functions.CopyApplier; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class KimahriValiantGuardian extends CardImpl { + + public KimahriValiantGuardian(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.CAT); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Ronso Rage -- At the beginning of combat on your turn, put a +1/+1 counter on Kimahri and tap target creature an opponent controls. Then you may have Kimahri become a copy of that creature, except its name is Kimahri, Valiant Guardian and it has vigilance and this ability. + this.addAbility(makeAbility()); + } + + private KimahriValiantGuardian(final KimahriValiantGuardian card) { + super(card); + } + + @Override + public KimahriValiantGuardian copy() { + return new KimahriValiantGuardian(this); + } + + static Ability makeAbility() { + Ability ability = new BeginningOfCombatTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()) + ); + ability.addEffect(new TapTargetEffect().concatBy("and")); + ability.addEffect(new KimahriValiantGuardianEffect()); + ability.addTarget(new TargetOpponentsCreaturePermanent()); + return ability.withFlavorWord("Ronso Rage"); + } +} + +class KimahriValiantGuardianEffect extends OneShotEffect { + + private static final CopyApplier applier = new CopyApplier() { + @Override + public boolean apply(Game game, MageObject blueprint, Ability source, UUID copyToObjectId) { + blueprint.setName("Kimahri, Valiant Guardian"); + blueprint.getAbilities().add(VigilanceAbility.getInstance()); + blueprint.getAbilities().add(KimahriValiantGuardian.makeAbility()); + return true; + } + }; + + KimahriValiantGuardianEffect() { + super(Outcome.Benefit); + staticText = "Then you may have {this} become a copy of that creature, " + + "except its name is Kimahri, Valiant Guardian and it has vigilance and this ability."; + } + + private KimahriValiantGuardianEffect(final KimahriValiantGuardianEffect effect) { + super(effect); + } + + @Override + public KimahriValiantGuardianEffect copy() { + return new KimahriValiantGuardianEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + Permanent creature = game.getPermanent(getTargetPointer().getFirst(game, source)); + return player != null + && permanent != null + && creature != null + && player.chooseUse(Outcome.Copy, "Have " + permanent.getIdName() + + " become a copy of " + creature.getIdName() + '?', source, game) + && game.copyPermanent(Duration.Custom, creature, permanent.getId(), source, applier) != null; + } +} diff --git a/Mage.Sets/src/mage/cards/k/KolodinTriumphCaster.java b/Mage.Sets/src/mage/cards/k/KolodinTriumphCaster.java index dd4b4110415..b3125425721 100644 --- a/Mage.Sets/src/mage/cards/k/KolodinTriumphCaster.java +++ b/Mage.Sets/src/mage/cards/k/KolodinTriumphCaster.java @@ -2,8 +2,10 @@ package mage.cards.k; import java.util.UUID; import mage.MageInt; +import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.Effect; import mage.abilities.effects.common.SaddleTargetMountEffect; import mage.abilities.effects.common.continuous.AddCardTypeTargetEffect; import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; @@ -15,6 +17,7 @@ import mage.filter.FilterPermanent; import mage.filter.StaticFilters; import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.Predicates; +import mage.target.targetpointer.TargetPointer; /** * @@ -48,13 +51,13 @@ public final class KolodinTriumphCaster extends CardImpl { new GainAbilityControlledEffect(HasteAbility.getInstance(), Duration.WhileOnBattlefield, filter))); // Whenever a Mount you control enters, it becomes saddled until end of turn. - this.addAbility(new EntersBattlefieldControlledTriggeredAbility(new SaddleTargetMountEffect("it becomes saddled until end of turn"), - mountFilter)); + this.addAbility(new EntersBattlefieldControlledTriggeredAbility(Zone.BATTLEFIELD, new SaddleTargetMountEffect("it becomes saddled until end of turn"), + mountFilter, false, SetTargetPointer.PERMANENT)); // Whenever a Vehicle you control enters, it becomes an artifact creature until end of turn. - this.addAbility(new EntersBattlefieldControlledTriggeredAbility(new AddCardTypeTargetEffect( - Duration.EndOfTurn, CardType.ARTIFACT, CardType.CREATURE) - .setText("it becomes an artifact creature until end of turn"), vehicleFilter) - ); + Effect effect = new AddCardTypeTargetEffect(Duration.EndOfTurn, CardType.ARTIFACT, CardType.CREATURE) + .setText("it becomes an artifact creature until end of turn"); + this.addAbility(new EntersBattlefieldControlledTriggeredAbility(Zone.BATTLEFIELD, effect, vehicleFilter, + false, SetTargetPointer.PERMANENT)); } private KolodinTriumphCaster(final KolodinTriumphCaster card) { diff --git a/Mage.Sets/src/mage/cards/k/KrileBaldesion.java b/Mage.Sets/src/mage/cards/k/KrileBaldesion.java new file mode 100644 index 00000000000..577dbb8e1aa --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KrileBaldesion.java @@ -0,0 +1,80 @@ +package mage.cards.k; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect; +import mage.abilities.keyword.LifelinkAbility; +import mage.cards.Card; +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.StaticFilters; +import mage.filter.common.FilterCreatureCard; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.game.Game; +import mage.game.stack.Spell; +import mage.target.common.TargetCardInYourGraveyard; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class KrileBaldesion extends CardImpl { + + private static final FilterCard filter = new FilterCreatureCard( + "creature card with mana value equal to that spell's mana value from your graveyard" + ); + + static { + filter.add(KrileBaldesionPredicate.instance); + } + + public KrileBaldesion(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.DWARF); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // Trace Aether -- Whenever you cast a noncreature spell, you may return target creature card with mana value equal to that spell's mana value from your graveyard to your hand. Do this only once each turn. + Ability ability = new SpellCastControllerTriggeredAbility( + new ReturnFromGraveyardToHandTargetEffect(), + StaticFilters.FILTER_SPELL_A_NON_CREATURE, true + ).setDoOnlyOnceEachTurn(true); + ability.addTarget(new TargetCardInYourGraveyard(filter)); + this.addAbility(ability.withFlavorWord("Trace Aether")); + } + + private KrileBaldesion(final KrileBaldesion card) { + super(card); + } + + @Override + public KrileBaldesion copy() { + return new KrileBaldesion(this); + } +} + +enum KrileBaldesionPredicate implements ObjectSourcePlayerPredicate { + instance; + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + return CardUtil + .getEffectValueFromAbility(input.getSource(), "spellCast", Spell.class) + .map(Spell::getManaValue) + .equals(input.getObject().getManaValue()); + } +} diff --git a/Mage.Sets/src/mage/cards/k/KrovodHaunch.java b/Mage.Sets/src/mage/cards/k/KrovodHaunch.java index f216e6c6bc9..ba7128a2889 100644 --- a/Mage.Sets/src/mage/cards/k/KrovodHaunch.java +++ b/Mage.Sets/src/mage/cards/k/KrovodHaunch.java @@ -31,7 +31,7 @@ public final class KrovodHaunch extends CardImpl { this.addAbility(new SimpleStaticAbility(new BoostEquippedEffect(2, 0))); // {2}, {T}, Sacrifice Krovod Haunch: You gain 3 life. - this.addAbility(new FoodAbility(true)); + this.addAbility(new FoodAbility()); // When Krovod Haunch is put into a graveyard from the battlefield, you may pay {1}{W}. If you do, create two 1/1 white Dog creature tokens. this.addAbility(new PutIntoGraveFromBattlefieldSourceTriggeredAbility( diff --git a/Mage.Sets/src/mage/cards/k/KruphixsInsight.java b/Mage.Sets/src/mage/cards/k/KruphixsInsight.java index a8339e6a364..51b68a61471 100644 --- a/Mage.Sets/src/mage/cards/k/KruphixsInsight.java +++ b/Mage.Sets/src/mage/cards/k/KruphixsInsight.java @@ -1,36 +1,30 @@ package mage.cards.k; -import java.util.UUID; -import mage.abilities.effects.Effect; import mage.abilities.effects.common.RevealLibraryPickControllerEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.PutCards; -import mage.filter.FilterCard; +import mage.filter.StaticFilters; + +import java.util.UUID; /** - * * @author LevelX2 */ public final class KruphixsInsight extends CardImpl { - private static final FilterCard filter = new FilterCard("enchantment cards"); - - static { - filter.add(CardType.ENCHANTMENT.getPredicate()); - } - public KruphixsInsight(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{2}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{G}"); // Reveal the top six cards of your library. Put up to three enchantment cards from among them into your hand // and the rest of the revealed cards into your graveyard. - Effect effect = new RevealLibraryPickControllerEffect(6, 3, filter, PutCards.HAND, PutCards.GRAVEYARD, false); - effect.setText("reveal the top six cards of your library. " + + this.getSpellAbility().addEffect(new RevealLibraryPickControllerEffect( + 6, 3, StaticFilters.FILTER_CARD_ENCHANTMENTS, + PutCards.HAND, PutCards.GRAVEYARD, false + ).setText("reveal the top six cards of your library. " + "Put up to three enchantment cards from among them into your hand " + - "and the rest of the revealed cards into your graveyard"); - this.getSpellAbility().addEffect(effect); + "and the rest of the revealed cards into your graveyard")); } private KruphixsInsight(final KruphixsInsight card) { diff --git a/Mage.Sets/src/mage/cards/k/KujaGenomeSorcerer.java b/Mage.Sets/src/mage/cards/k/KujaGenomeSorcerer.java new file mode 100644 index 00000000000..6784c390e47 --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KujaGenomeSorcerer.java @@ -0,0 +1,69 @@ +package mage.cards.k; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.hint.ConditionHint; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.abilities.keyword.TransformAbility; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +import mage.constants.ComparisonType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.common.FilterControlledPermanent; +import mage.game.permanent.token.BlackWizardToken; + +/** + * @author balazskristof + */ +public final class KujaGenomeSorcerer extends CardImpl { + + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.WIZARD); + private static final Condition condition = new PermanentsOnTheBattlefieldCondition(filter, ComparisonType.OR_GREATER, 4); + private static final Hint hint = new ValueHint("Wizards you control", new PermanentsOnBattlefieldCount(filter)); + + public KujaGenomeSorcerer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.MUTANT); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + this.secondSideCardClazz = mage.cards.t.TranceKujaFateDefied.class; + + // At the beginning of your end step, create a tapped 0/1 black Wizard creature token with "Whenever you cast a noncreature spell, this token deals 1 damage to each opponent.", Then if you control four or more Wizards, transform Kuja. + Ability ability = new BeginningOfEndStepTriggeredAbility( + new CreateTokenEffect(new BlackWizardToken(), 1, true) + ); + ability.addEffect(new ConditionalOneShotEffect( + new TransformSourceEffect(), + condition, + "if you control four or more Wizards, transform {this}" + ).concatBy("Then")); + ability.addHint(hint); + this.addAbility(ability); + this.addAbility(new TransformAbility()); + } + + private KujaGenomeSorcerer(final KujaGenomeSorcerer card) { + super(card); + } + + @Override + public KujaGenomeSorcerer copy() { + return new KujaGenomeSorcerer(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LasydProwler.java b/Mage.Sets/src/mage/cards/l/LasydProwler.java index 1971addf543..6bc9ea3e2d1 100644 --- a/Mage.Sets/src/mage/cards/l/LasydProwler.java +++ b/Mage.Sets/src/mage/cards/l/LasydProwler.java @@ -30,7 +30,7 @@ import java.util.UUID; */ public final class LasydProwler extends CardImpl { - private static final DynamicValue xValue = new CardsInControllerGraveyardCount(StaticFilters.FILTER_CARD_LAND, null); + private static final DynamicValue xValue = new CardsInControllerGraveyardCount(StaticFilters.FILTER_CARD_LANDS, null); private static final Hint hint = new ValueHint("Land cards in your graveyard", xValue); public LasydProwler(UUID ownerId, CardSetInfo setInfo) { diff --git a/Mage.Sets/src/mage/cards/l/LaughingMad.java b/Mage.Sets/src/mage/cards/l/LaughingMad.java new file mode 100644 index 00000000000..280ef09f781 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LaughingMad.java @@ -0,0 +1,39 @@ +package mage.cards.l; + +import mage.abilities.costs.common.DiscardCardCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.keyword.FlashbackAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LaughingMad extends CardImpl { + + public LaughingMad(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{R}"); + + // As an additional cost to cast this spell, discard a card. + this.getSpellAbility().addCost(new DiscardCardCost()); + + // Draw two cards. + this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(2)); + + // Flashback {3}{R} + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{3}{R}"))); + } + + private LaughingMad(final LaughingMad card) { + super(card); + } + + @Override + public LaughingMad copy() { + return new LaughingMad(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LaviniaAzoriusRenegade.java b/Mage.Sets/src/mage/cards/l/LaviniaAzoriusRenegade.java index 5820d24f257..5dd5ac6e8d5 100644 --- a/Mage.Sets/src/mage/cards/l/LaviniaAzoriusRenegade.java +++ b/Mage.Sets/src/mage/cards/l/LaviniaAzoriusRenegade.java @@ -1,11 +1,10 @@ - package mage.cards.l; import mage.MageInt; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.common.SpellCastOpponentNoManaSpentTriggeredAbility; +import mage.abilities.common.SpellCastOpponentTriggeredAbility; import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; import mage.abilities.effects.common.CounterTargetEffect; import mage.cards.Card; @@ -24,13 +23,12 @@ import java.util.List; import java.util.UUID; /** - * * @author NinthWorld */ public final class LaviniaAzoriusRenegade extends CardImpl { public LaviniaAzoriusRenegade(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{W}{U}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W}{U}"); this.subtype.add(SubType.HUMAN); this.subtype.add(SubType.SOLDIER); this.supertype.add(SuperType.LEGENDARY); @@ -42,7 +40,10 @@ public final class LaviniaAzoriusRenegade extends CardImpl { this.addAbility(new SimpleStaticAbility(new LaviniaAzoriusRenegadeReplacementEffect())); // Whenever an opponent casts a spell, if no mana was spent to cast it, counter that spell. - this.addAbility(new SpellCastOpponentNoManaSpentTriggeredAbility(new CounterTargetEffect().setText("counter that spell"))); + this.addAbility(new SpellCastOpponentTriggeredAbility( + Zone.BATTLEFIELD, new CounterTargetEffect(), + StaticFilters.FILTER_SPELL_NO_MANA_SPENT, false, true + )); } private LaviniaAzoriusRenegade(final LaviniaAzoriusRenegade card) { @@ -96,7 +97,7 @@ class LaviniaAzoriusRenegadeReplacementEffect extends ContinuousRuleModifyingEff private int getLandCount(Ability source, GameEvent event, Game game) { int landCount = 0; UUID playerId = event.getPlayerId(); - if(playerId != null) { + if (playerId != null) { List permanents = game.getBattlefield().getActivePermanents(StaticFilters.FILTER_LAND, playerId, source, game); for (Permanent permanent : permanents) { if (permanent.isControlledBy(playerId)) { diff --git a/Mage.Sets/src/mage/cards/l/LazotepPlating.java b/Mage.Sets/src/mage/cards/l/LazotepPlating.java index d192d8f2eeb..99cf8ca0765 100644 --- a/Mage.Sets/src/mage/cards/l/LazotepPlating.java +++ b/Mage.Sets/src/mage/cards/l/LazotepPlating.java @@ -1,17 +1,18 @@ package mage.cards.l; +import mage.abilities.effects.Effect; import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.effects.common.continuous.GainAbilityControllerEffect; import mage.abilities.effects.keyword.AmassEffect; import mage.abilities.keyword.HexproofAbility; 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; -import mage.abilities.effects.Effect; -import mage.abilities.effects.common.continuous.GainAbilityControllerEffect; -import mage.constants.SubType; /** * @author TheElk801 @@ -29,7 +30,7 @@ public final class LazotepPlating extends CardImpl { HexproofAbility.getInstance(), Duration.EndOfTurn ); Effect effect2 = new GainAbilityControlledEffect( - HexproofAbility.getInstance(), Duration.EndOfTurn + HexproofAbility.getInstance(), Duration.EndOfTurn, StaticFilters.FILTER_PERMANENTS ); effect.setText("You"); effect2.setText("and permanents you control gain hexproof until end of turn"); diff --git a/Mage.Sets/src/mage/cards/l/Lembas.java b/Mage.Sets/src/mage/cards/l/Lembas.java index ac3e80eed4a..4a746335157 100644 --- a/Mage.Sets/src/mage/cards/l/Lembas.java +++ b/Mage.Sets/src/mage/cards/l/Lembas.java @@ -30,7 +30,7 @@ public final class Lembas extends CardImpl { this.addAbility(ability); // {2}, {T}, Sacrifice Lembas: You gain 3 life. - this.addAbility(new FoodAbility(true)); + this.addAbility(new FoodAbility()); // When Lembas is put into a graveyard from the battlefield, its owner shuffles it into their library. this.addAbility(new PutIntoGraveFromBattlefieldSourceTriggeredAbility( diff --git a/Mage.Sets/src/mage/cards/l/LethalScheme.java b/Mage.Sets/src/mage/cards/l/LethalScheme.java index 2d54ccb2b51..03c07511b81 100644 --- a/Mage.Sets/src/mage/cards/l/LethalScheme.java +++ b/Mage.Sets/src/mage/cards/l/LethalScheme.java @@ -54,7 +54,7 @@ class LethalSchemeEffect extends OneShotEffect { LethalSchemeEffect() { super(Outcome.Benefit); - this.staticText = "Each creature that convoked {this} connives."; + this.staticText = "Each creature that convoked this spell connives."; } private LethalSchemeEffect(final LethalSchemeEffect effect) { diff --git a/Mage.Sets/src/mage/cards/l/LeylineTyrant.java b/Mage.Sets/src/mage/cards/l/LeylineTyrant.java index 79ea7dd9264..26b38cbb819 100644 --- a/Mage.Sets/src/mage/cards/l/LeylineTyrant.java +++ b/Mage.Sets/src/mage/cards/l/LeylineTyrant.java @@ -111,10 +111,8 @@ class LeylineTyrantDamageEffect extends OneShotEffect { if (player == null) { return false; } - int costX = player.announceXMana( - 0, Integer.MAX_VALUE, - "Announce the value for {X}", game, source - ); + // TODO: add some AI hints by min/max values + int costX = player.announceX(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source, true); String manaString; if (costX == 0) { manaString = "{0}"; diff --git a/Mage.Sets/src/mage/cards/l/LifecraftCavalry.java b/Mage.Sets/src/mage/cards/l/LifecraftCavalry.java index 7bf234eb7cf..2d6080a9757 100644 --- a/Mage.Sets/src/mage/cards/l/LifecraftCavalry.java +++ b/Mage.Sets/src/mage/cards/l/LifecraftCavalry.java @@ -1,7 +1,5 @@ - package mage.cards.l; -import java.util.UUID; import mage.MageInt; import mage.abilities.common.EntersBattlefieldAbility; import mage.abilities.condition.common.RevoltCondition; @@ -9,13 +7,15 @@ import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.keyword.TrampleAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.constants.AbilityWord; import mage.constants.CardType; import mage.constants.SubType; import mage.counters.CounterType; import mage.watchers.common.RevoltWatcher; +import java.util.UUID; + /** - * * @author fireshoes */ public final class LifecraftCavalry extends CardImpl { @@ -32,15 +32,11 @@ public final class LifecraftCavalry extends CardImpl { this.addAbility(TrampleAbility.getInstance()); // Revolt — Lifecraft Cavalry enters the battlefield with two +1/+1 counters on it if a permanent you controlled left the battlefield this turn. - this.addAbility( - new EntersBattlefieldAbility( - new AddCountersSourceEffect(CounterType.P1P1.createInstance(2)), - false, - RevoltCondition.instance, - "Revolt — {this} enters with two +1/+1 counters on it if a permanent you controlled left the battlefield this turn.", - null).addHint(RevoltCondition.getHint()), - new RevoltWatcher() - ); + this.addAbility(new EntersBattlefieldAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance(2)), false, + RevoltCondition.instance, "{this} enters with two +1/+1 counters on it " + + "if a permanent you controlled left the battlefield this turn.", null + ).setAbilityWord(AbilityWord.REVOLT).addHint(RevoltCondition.getHint()), new RevoltWatcher()); } private LifecraftCavalry(final LifecraftCavalry card) { diff --git a/Mage.Sets/src/mage/cards/l/LightningSecuritySergeant.java b/Mage.Sets/src/mage/cards/l/LightningSecuritySergeant.java new file mode 100644 index 00000000000..f2f7e3461a7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LightningSecuritySergeant.java @@ -0,0 +1,47 @@ +package mage.cards.l; + +import mage.MageInt; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.effects.common.ExileTopXMayPlayUntilEffect; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LightningSecuritySergeant extends CardImpl { + + public LightningSecuritySergeant(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Menace + this.addAbility(new MenaceAbility()); + + // Whenever Lightning deals combat damage to a player, exile the top card of your library. You may play that card for as long as you control Lightning. + this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility( + new ExileTopXMayPlayUntilEffect(1, Duration.WhileControlled) + )); + } + + private LightningSecuritySergeant(final LightningSecuritySergeant card) { + super(card); + } + + @Override + public LightningSecuritySergeant copy() { + return new LightningSecuritySergeant(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LocalizedDestruction.java b/Mage.Sets/src/mage/cards/l/LocalizedDestruction.java index 1c4ad664032..09f9f9c403e 100644 --- a/Mage.Sets/src/mage/cards/l/LocalizedDestruction.java +++ b/Mage.Sets/src/mage/cards/l/LocalizedDestruction.java @@ -88,7 +88,7 @@ class LocalizedDestructionEffect extends OneShotEffect { } int numberToPay = controller.getAmount(1, totalEnergy, - "Pay one or more {E}", game); + "Pay one or more {E}", source, game); Cost cost = new PayEnergyCost(numberToPay); diff --git a/Mage.Sets/src/mage/cards/l/LordJyscalGuado.java b/Mage.Sets/src/mage/cards/l/LordJyscalGuado.java new file mode 100644 index 00000000000..4303d1247ca --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LordJyscalGuado.java @@ -0,0 +1,108 @@ +package mage.cards.l; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.abilities.effects.keyword.InvestigateEffect; +import mage.abilities.hint.ConditionHint; +import mage.abilities.hint.Hint; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.WatcherScope; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.watchers.Watcher; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LordJyscalGuado extends CardImpl { + + public LordJyscalGuado(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPIRIT); + this.subtype.add(SubType.CLERIC); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // At the beginning of each end step, if you put a counter on a creature this turn, investigate. + this.addAbility(new BeginningOfEndStepTriggeredAbility(new InvestigateEffect()) + .withInterveningIf(LordJyscalGuadoCondition.instance) + .addHint(LordJyscalGuadoCondition.getHint()), + new LordJyscalGuadoWatcher() + ); + } + + private LordJyscalGuado(final LordJyscalGuado card) { + super(card); + } + + @Override + public LordJyscalGuado copy() { + return new LordJyscalGuado(this); + } +} + +enum LordJyscalGuadoCondition implements Condition { + instance; + private static final Hint hint = new ConditionHint(instance); + + @Override + public boolean apply(Game game, Ability source) { + return LordJyscalGuadoWatcher.checkPlayer(source.getControllerId(), game); + } + + @Override + public String toString() { + return "you put a counter on a creature this turn"; + } + + public static Hint getHint() { + return hint; + } +} + +class LordJyscalGuadoWatcher extends Watcher { + + Set set = new HashSet<>(); + + LordJyscalGuadoWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() != GameEvent.EventType.COUNTER_ADDED) { + return; + } + Permanent permanent = game.getPermanent(event.getTargetId()); + if (permanent != null && permanent.isCreature(game)) { + set.add(event.getPlayerId()); + } + } + + @Override + public void reset() { + super.reset(); + set.clear(); + } + + static boolean checkPlayer(UUID playerId, Game game) { + return game.getState().getWatcher(LordJyscalGuadoWatcher.class).set.contains(playerId); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LoyalApprentice.java b/Mage.Sets/src/mage/cards/l/LoyalApprentice.java index e918b817627..8ef7bab506a 100644 --- a/Mage.Sets/src/mage/cards/l/LoyalApprentice.java +++ b/Mage.Sets/src/mage/cards/l/LoyalApprentice.java @@ -53,6 +53,7 @@ class LoyalApprenticeEffect extends OneShotEffect { LoyalApprenticeEffect() { super(Outcome.Benefit); + staticText = "create a 1/1 colorless Thopter artifact creature token with flying. That token gains haste until end of turn"; } private LoyalApprenticeEffect(final LoyalApprenticeEffect effect) { diff --git a/Mage.Sets/src/mage/cards/l/LuciusTheEternal.java b/Mage.Sets/src/mage/cards/l/LuciusTheEternal.java new file mode 100644 index 00000000000..bcc094792d1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LuciusTheEternal.java @@ -0,0 +1,143 @@ +package mage.cards.l; + +import mage.MageInt; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.common.DiesSourceTriggeredAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.effects.common.ExileSourceEffect; +import mage.abilities.effects.common.InfoEffect; +import mage.abilities.effects.common.ReturnFromExileEffect; +import mage.abilities.keyword.HasteAbility; +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.target.common.TargetOpponentsCreaturePermanent; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class LuciusTheEternal extends CardImpl { + + public LuciusTheEternal(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.ASTARTES); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(5); + this.toughness = new MageInt(3); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // Armor of Shrieking Souls -- When Lucius the Eternal dies, exile it and choose target creature an opponent controls. When that creature leaves the battlefield, return Lucius the Eternal from exile to the battlefield under its owner's control. + Ability ability = new DiesSourceTriggeredAbility(new LuciusTheEternalEffect()); + ability.addTarget(new TargetOpponentsCreaturePermanent()); + this.addAbility(ability.withFlavorWord("Armor of Shrieking Souls")); + } + + private LuciusTheEternal(final LuciusTheEternal card) { + super(card); + } + + @Override + public LuciusTheEternal copy() { + return new LuciusTheEternal(this); + } +} + +class LuciusTheEternalEffect extends OneShotEffect { + + LuciusTheEternalEffect() { + super(Outcome.Detriment); + staticText = "exile it and choose target creature an opponent controls. " + + "When that creature leaves the battlefield, " + + "return this card from exile to the battlefield under its owner's control."; + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(source.getFirstTarget()); + if (permanent == null) { + return false; + } + + // exile Lucius (note: it may fail, we still have to create a delayed trigger) + boolean exiled = new ExileSourceEffect(true).apply(game, source); + game.processAction(); + + new CreateDelayedTriggeredAbilityEffect( + new LuciusTheEternalDelayedTriggeredAbility( + new ReturnFromExileEffect(Zone.BATTLEFIELD) + ) + ).setTargetPointer(getTargetPointer().copy()).apply(game, source); + + // For better feedback, add an info to the targetted permanent. + String luciusDescription = exiled ? CardUtil.getSourceIdName(game, source) : CardUtil.getSourceName(game, source); + InfoEffect.addInfoToPermanent(game, source, permanent, "(When this creature leaves the battlefield, " + + "return " + luciusDescription + " from exile to the battlefield under its owner's control)"); + + return true; + } + + private LuciusTheEternalEffect(final LuciusTheEternalEffect effect) { + super(effect); + } + + @Override + public LuciusTheEternalEffect copy() { + return new LuciusTheEternalEffect(this); + } +} + +class LuciusTheEternalDelayedTriggeredAbility extends DelayedTriggeredAbility { + + private MageObjectReference mor; + + LuciusTheEternalDelayedTriggeredAbility(Effect effect) { + super(effect, Duration.WhileOnBattlefield, true); + setTriggerPhrase("When that creature leaves the battlefield, "); + setLeavesTheBattlefieldTrigger(true); + } + + protected LuciusTheEternalDelayedTriggeredAbility(final LuciusTheEternalDelayedTriggeredAbility ability) { + super(ability); + this.mor = ability.mor; + } + + @Override + public LuciusTheEternalDelayedTriggeredAbility copy() { + return new LuciusTheEternalDelayedTriggeredAbility(this); + } + + @Override + public void init(Game game) { + mor = new MageObjectReference(getFirstTarget(), game); + getTargets().clear(); + } + + @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; + Permanent permanent = zEvent.getTarget(); + return Zone.BATTLEFIELD.equals(zEvent.getFromZone()) + && mor != null + && mor.refersTo(permanent, game); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LuluSternGuardian.java b/Mage.Sets/src/mage/cards/l/LuluSternGuardian.java new file mode 100644 index 00000000000..d5459a64825 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LuluSternGuardian.java @@ -0,0 +1,101 @@ +package mage.cards.l; + +import mage.MageInt; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +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.SuperType; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LuluSternGuardian extends CardImpl { + + public LuluSternGuardian(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Whenever an opponent attacks you, choose target creature attacking you. Put a stun counter on that creature. + this.addAbility(new LuluSternGuardianTriggeredAbility()); + + // {3}{U}: Proliferate. + this.addAbility(new SimpleActivatedAbility(new ProliferateEffect(), new ManaCostsImpl<>("{3}{U}"))); + } + + private LuluSternGuardian(final LuluSternGuardian card) { + super(card); + } + + @Override + public LuluSternGuardian copy() { + return new LuluSternGuardian(this); + } +} + +enum LuluSternGuardianPredicate implements ObjectSourcePlayerPredicate { + instance; + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + return input.getSource().isControlledBy(game.getCombat().getDefenderId(input.getObject().getId())); + } +} + +class LuluSternGuardianTriggeredAbility extends TriggeredAbilityImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent("creature attacking you"); + + static { + filter.add(LuluSternGuardianPredicate.instance); + } + + LuluSternGuardianTriggeredAbility() { + super(Zone.BATTLEFIELD, new AddCountersTargetEffect(CounterType.STUN.createInstance()) + .setText("choose target creature attacking you. Put a stun counter on that creature")); + setTriggerPhrase("Whenever an opponent attacks you, "); + addTarget(new TargetPermanent(filter)); + } + + private LuluSternGuardianTriggeredAbility(final LuluSternGuardianTriggeredAbility ability) { + super(ability); + } + + @Override + public LuluSternGuardianTriggeredAbility copy() { + return new LuluSternGuardianTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DEFENDER_ATTACKED; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return isControlledBy(event.getTargetId()) + && game.getOpponents(getControllerId()).contains(event.getPlayerId()); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MachinistsArsenal.java b/Mage.Sets/src/mage/cards/m/MachinistsArsenal.java new file mode 100644 index 00000000000..56df78e2728 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MachinistsArsenal.java @@ -0,0 +1,56 @@ +package mage.cards.m; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.MultipliedValue; +import mage.abilities.dynamicvalue.common.ArtifactYouControlCount; +import mage.abilities.effects.common.continuous.AddCardSubtypeAttachedEffect; +import mage.abilities.effects.common.continuous.BoostEquippedEffect; +import mage.abilities.hint.common.ArtifactYouControlHint; +import mage.abilities.keyword.EquipAbility; +import mage.abilities.keyword.JobSelectAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AttachmentType; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MachinistsArsenal extends CardImpl { + + private static final DynamicValue xValue = new MultipliedValue(ArtifactYouControlCount.instance, 2); + + public MachinistsArsenal(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{4}{W}"); + + this.subtype.add(SubType.EQUIPMENT); + + // Job select + this.addAbility(new JobSelectAbility()); + + // Equipped creature gets +2/+2 for each artifact you control and is an Artificer in addition to its other types. + Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(xValue, xValue) + .setText("equipped creature gets +2/+2 for each artifact you control")); + ability.addEffect(new AddCardSubtypeAttachedEffect( + SubType.ARTIFICER, AttachmentType.EQUIPMENT + ).setText(", and is a Artificer in addition to its other types")); + this.addAbility(ability.addHint(ArtifactYouControlHint.instance)); + + // Machina -- Equip {4} + this.addAbility(new EquipAbility(4).withFlavorWord("Machina")); + } + + private MachinistsArsenal(final MachinistsArsenal card) { + super(card); + } + + @Override + public MachinistsArsenal copy() { + return new MachinistsArsenal(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MagesContest.java b/Mage.Sets/src/mage/cards/m/MagesContest.java index 51968f24497..bea7b9b6544 100644 --- a/Mage.Sets/src/mage/cards/m/MagesContest.java +++ b/Mage.Sets/src/mage/cards/m/MagesContest.java @@ -79,7 +79,7 @@ class MagesContestEffect extends OneShotEffect { } } else if (currentPlayer.chooseUse(Outcome.Benefit, winner.getLogName() + " has bet " + highBid + " life. Top the bid?", source, game)) { // Human choose - newBid = currentPlayer.getAmount(highBid + 1, Integer.MAX_VALUE, "Choose bid", game); + newBid = currentPlayer.getAmount(highBid + 1, Integer.MAX_VALUE, "Choose bid", source, game); } if (newBid > highBid) { highBid = newBid; diff --git a/Mage.Sets/src/mage/cards/m/MagitekScythe.java b/Mage.Sets/src/mage/cards/m/MagitekScythe.java new file mode 100644 index 00000000000..41dc6347132 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MagitekScythe.java @@ -0,0 +1,57 @@ +package mage.cards.m; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.combat.MustBeBlockedByAtLeastOneTargetEffect; +import mage.abilities.effects.common.continuous.BoostEquippedEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.EquipAbility; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MagitekScythe extends CardImpl { + + public MagitekScythe(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{4}"); + + this.subtype.add(SubType.EQUIPMENT); + + // A Test of Your Reflexes! -- When this Equipment enters, you may attach it to target creature you control. If you do, that creature gains first strike until end of turn and must be blocked this turn if able. + Ability ability = new EntersBattlefieldTriggeredAbility(new AttachEffect( + Outcome.BoostCreature, "attach it to target creature you control" + ), true); + ability.addTarget(new TargetControlledCreaturePermanent()); + ability.addEffect(new GainAbilityTargetEffect(FirstStrikeAbility.getInstance()) + .setText("If you do, that creature gains first strike until end of turn")); + ability.addEffect(new MustBeBlockedByAtLeastOneTargetEffect() + .setText("and must be blocked this turn if able")); + this.addAbility(ability.withFlavorWord("A Test of Your Reflexes!")); + + // Equipped creature gets +2/+1. + this.addAbility(new SimpleStaticAbility(new BoostEquippedEffect(2, 1))); + + // Equip {2} + this.addAbility(new EquipAbility(2)); + } + + private MagitekScythe(final MagitekScythe card) { + super(card); + } + + @Override + public MagitekScythe copy() { + return new MagitekScythe(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/Malboro.java b/Mage.Sets/src/mage/cards/m/Malboro.java new file mode 100644 index 00000000000..19769b1f7ae --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/Malboro.java @@ -0,0 +1,78 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.LoseLifeOpponentsEffect; +import mage.abilities.effects.common.discard.DiscardEachPlayerEffect; +import mage.abilities.keyword.SwampcyclingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.players.Player; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Malboro extends CardImpl { + + public Malboro(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{B}{B}"); + + this.subtype.add(SubType.PLANT); + this.subtype.add(SubType.HORROR); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Bad Breath -- When this creature enters, each opponent discards a card, loses 2 life, and exiles the top three cards of their library. + Ability ability = new EntersBattlefieldTriggeredAbility(new DiscardEachPlayerEffect(TargetController.OPPONENT)); + ability.addEffect(new LoseLifeOpponentsEffect(2).setText(", loses 2 life")); + ability.addEffect(new MalboroEffect()); + this.addAbility(ability.withFlavorWord("Bad Breath")); + + // Swampcycling {2} + this.addAbility(new SwampcyclingAbility(new ManaCostsImpl<>("{2}"))); + } + + private Malboro(final Malboro card) { + super(card); + } + + @Override + public Malboro copy() { + return new Malboro(this); + } +} + +class MalboroEffect extends OneShotEffect { + + MalboroEffect() { + super(Outcome.Benefit); + staticText = ", and exiles the top three cards of their library"; + } + + private MalboroEffect(final MalboroEffect effect) { + super(effect); + } + + @Override + public MalboroEffect copy() { + return new MalboroEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + for (UUID opponentId : game.getOpponents(source.getControllerId())) { + Player player = game.getPlayer(opponentId); + if (player != null) { + player.moveCards(player.getLibrary().getTopCards(game, 3), Zone.EXILED, source, game); + } + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/m/MarchOfTheWorldOoze.java b/Mage.Sets/src/mage/cards/m/MarchOfTheWorldOoze.java index 7cb1ae1f446..44328e1b21f 100644 --- a/Mage.Sets/src/mage/cards/m/MarchOfTheWorldOoze.java +++ b/Mage.Sets/src/mage/cards/m/MarchOfTheWorldOoze.java @@ -1,49 +1,52 @@ package mage.cards.m; -import java.util.UUID; - import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.common.SpellCastOpponentNoManaSpentTriggeredAbility; import mage.abilities.common.SpellCastOpponentTriggeredAbility; -import mage.abilities.condition.common.OnOpponentsTurnCondition; -import mage.abilities.effects.common.CastSourceTriggeredAbility; -import mage.abilities.effects.common.CreateTokenAllEffect; -import mage.abilities.effects.common.CreateTokenControllerTargetEffect; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.continuous.AddCardSubtypeAllEffect; import mage.abilities.effects.common.continuous.SetBasePowerToughnessAllEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.TargetController; import mage.filter.FilterSpell; import mage.filter.StaticFilters; import mage.game.permanent.token.ElephantToken; +import java.util.UUID; + /** - * * @author Jmlundeen */ public final class MarchOfTheWorldOoze extends CardImpl { + public static final FilterSpell filter = new FilterSpell("a spell, if it's not their turn"); static { filter.add(TargetController.INACTIVE.getControllerPredicate()); } + public MarchOfTheWorldOoze(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{G}{G}{G}"); - // Creatures you control have base power and toughness 6/6 and are Oozes in addition to their other types. Ability ability = new SimpleStaticAbility(new SetBasePowerToughnessAllEffect( - 6, 6, Duration.WhileOnBattlefield, StaticFilters.FILTER_CONTROLLED_CREATURE)); - ability.addEffect(new AddCardSubtypeAllEffect(StaticFilters.FILTER_CONTROLLED_CREATURE, SubType.OOZE, null) - .concatBy("and")); + 6, 6, Duration.WhileOnBattlefield, StaticFilters.FILTER_CONTROLLED_CREATURE + )); + ability.addEffect(new AddCardSubtypeAllEffect( + StaticFilters.FILTER_CONTROLLED_CREATURE, SubType.OOZE, null + ).concatBy("and")); this.addAbility(ability); + // Whenever an opponent casts a spell, if it's not their turn, you create a 3/3 green Elephant creature token. - Ability ability2 = new SpellCastOpponentTriggeredAbility(new CreateTokenEffect(new ElephantToken()) - .setText("you create a 3/3 green Elephant creature token"), - filter, false); + Ability ability2 = new SpellCastOpponentTriggeredAbility( + new CreateTokenEffect(new ElephantToken()) + .setText("you create a 3/3 green Elephant creature token"), + filter, false + ); this.addAbility(ability2); } diff --git a/Mage.Sets/src/mage/cards/m/MarduSiegebreaker.java b/Mage.Sets/src/mage/cards/m/MarduSiegebreaker.java index ead29290170..f101314369a 100644 --- a/Mage.Sets/src/mage/cards/m/MarduSiegebreaker.java +++ b/Mage.Sets/src/mage/cards/m/MarduSiegebreaker.java @@ -18,7 +18,10 @@ import mage.cards.Cards; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; +import mage.filter.FilterPermanent; import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; import mage.game.Game; import mage.game.GameState; import mage.game.permanent.token.Token; @@ -39,6 +42,12 @@ import java.util.UUID; */ public final class MarduSiegebreaker extends CardImpl { + private static final FilterPermanent filter = new FilterControlledCreaturePermanent("other target creature you control"); + + static { + filter.add(AnotherPredicate.instance); + } + public MarduSiegebreaker(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}{W}{B}"); @@ -55,7 +64,7 @@ public final class MarduSiegebreaker extends CardImpl { // When this creature enters, exile up to one other target creature you control until this creature leaves the battlefield. Ability ability = new EntersBattlefieldTriggeredAbility(new ExileUntilSourceLeavesEffect()); - ability.addTarget(new TargetPermanent(0, 1, StaticFilters.FILTER_OTHER_CONTROLLED_CREATURE)); + ability.addTarget(new TargetPermanent(0, 1, filter)); this.addAbility(ability); // Whenever this creature attacks, for each opponent, create a tapped token that's a copy of the exiled card attacking that opponent. At the beginning of your end step, sacrifice those tokens. @@ -77,7 +86,7 @@ class MarduSiegebreakerEffect extends OneShotEffect { MarduSiegebreakerEffect() { super(Outcome.Benefit); staticText = "for each opponent, create a tapped token that's a copy of the exiled card " + - "attacking that opponent. At the beginning of your end step, sacrifice those tokens"; + "attacking that opponent. At the beginning of your next end step, sacrifice those tokens"; } private MarduSiegebreakerEffect(final MarduSiegebreakerEffect effect) { diff --git a/Mage.Sets/src/mage/cards/m/MarinaVendrell.java b/Mage.Sets/src/mage/cards/m/MarinaVendrell.java new file mode 100644 index 00000000000..133b9bd167c --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MarinaVendrell.java @@ -0,0 +1,58 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.effects.common.LockOrUnlockRoomTargetEffect; +import mage.abilities.effects.common.RevealLibraryPutIntoHandEffect; +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.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledPermanent; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MarinaVendrell extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledPermanent(SubType.ROOM); + + public MarinaVendrell(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W}{U}{B}{R}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARLOCK); + this.power = new MageInt(3); + this.toughness = new MageInt(5); + + // When Marina Vendrell enters, reveal the top seven cards of your library. Put all enchantment cards from among them into your hand and the rest on the bottom of your library in a random order. + this.addAbility(new EntersBattlefieldTriggeredAbility(new RevealLibraryPutIntoHandEffect( + 7, StaticFilters.FILTER_CARD_ENCHANTMENTS, Zone.LIBRARY, false + ))); + + // {T}: Lock or unlock a door of target Room you control. Activate only as a sorcery. + Ability ability = new ActivateAsSorceryActivatedAbility(new LockOrUnlockRoomTargetEffect(), new TapSourceCost()); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + } + + private MarinaVendrell(final MarinaVendrell card) { + super(card); + } + + @Override + public MarinaVendrell copy() { + return new MarinaVendrell(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MatoyaArchonElder.java b/Mage.Sets/src/mage/cards/m/MatoyaArchonElder.java new file mode 100644 index 00000000000..bff96bb6db8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MatoyaArchonElder.java @@ -0,0 +1,76 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +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 java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MatoyaArchonElder extends CardImpl { + + public MatoyaArchonElder(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARLOCK); + this.power = new MageInt(1); + this.toughness = new MageInt(4); + + // Whenever you scry or surveil, draw a card. + this.addAbility(new MatoyaArchonElderTriggeredAbility()); + } + + private MatoyaArchonElder(final MatoyaArchonElder card) { + super(card); + } + + @Override + public MatoyaArchonElder copy() { + return new MatoyaArchonElder(this); + } +} + +class MatoyaArchonElderTriggeredAbility extends TriggeredAbilityImpl { + + MatoyaArchonElderTriggeredAbility() { + super(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1)); + setTriggerPhrase("Whenever you scry or surveil, "); + } + + private MatoyaArchonElderTriggeredAbility(final MatoyaArchonElderTriggeredAbility ability) { + super(ability); + } + + @Override + public MatoyaArchonElderTriggeredAbility copy() { + return new MatoyaArchonElderTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + switch (event.getType()) { + case SCRIED: + case SURVEILED: + return true; + default: + return false; + } + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return isControlledBy(event.getPlayerId()); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MeTheImmortal.java b/Mage.Sets/src/mage/cards/m/MeTheImmortal.java index 33c04b6f460..fe21e7d6279 100644 --- a/Mage.Sets/src/mage/cards/m/MeTheImmortal.java +++ b/Mage.Sets/src/mage/cards/m/MeTheImmortal.java @@ -138,7 +138,7 @@ class MeTheImmortalCastEffect extends AsThoughEffectImpl { MeTheImmortalCastEffect() { super(AsThoughEffectType.CAST_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfGame, Outcome.Benefit); - this.staticText = "you may cast {this} from your graveyard " + + this.staticText = "you may cast this card from your graveyard " + "by discarding two cards in addition to paying its other costs"; } diff --git a/Mage.Sets/src/mage/cards/m/Melee.java b/Mage.Sets/src/mage/cards/m/Melee.java index dfa3904ce31..1ff88665cb3 100644 --- a/Mage.Sets/src/mage/cards/m/Melee.java +++ b/Mage.Sets/src/mage/cards/m/Melee.java @@ -45,7 +45,7 @@ public final class Melee extends CardImpl { // Cast Melee only during your turn and only during combat before blockers are declared. this.addAbility(new CastOnlyDuringPhaseStepSourceAbility( null, null, condition, - "Cast this spell only during your turn and only during combat before blockers are declared" + "Cast this spell only during combat on your turn before blockers are declared" ).addHint(hint)); // You choose which creatures block this combat and how those creatures block. diff --git a/Mage.Sets/src/mage/cards/m/MenacingOgre.java b/Mage.Sets/src/mage/cards/m/MenacingOgre.java index cf3015dbba9..9dc2c800e89 100644 --- a/Mage.Sets/src/mage/cards/m/MenacingOgre.java +++ b/Mage.Sets/src/mage/cards/m/MenacingOgre.java @@ -81,7 +81,7 @@ class MenacingOgreEffect extends OneShotEffect { for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { Player player = game.getPlayer(playerId); if (player != null) { - number = player.getAmount(0, 1000, message, game); + number = player.getAmount(0, 1000, message, source, game); numberChosen.put(player, number); } } diff --git a/Mage.Sets/src/mage/cards/m/MidnightCrusaderShuttle.java b/Mage.Sets/src/mage/cards/m/MidnightCrusaderShuttle.java new file mode 100644 index 00000000000..3dbb00d04d6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MidnightCrusaderShuttle.java @@ -0,0 +1,142 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.GainControlTargetEffect; +import mage.abilities.keyword.CrewAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.choices.FaceVillainousChoice; +import mage.choices.VillainousChoice; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.ControllerIdPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.targetpointer.FixedTarget; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MidnightCrusaderShuttle extends CardImpl { + + public MidnightCrusaderShuttle(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{4}"); + + this.subtype.add(SubType.VEHICLE); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // Midnight Entity -- Whenever Midnight Crusader Shuttle attacks, defending player faces a villainous choice -- That player sacrifices a creature, or you gain control of a creature of your choice that player controls until end of turn. If you gain control of a creature this way, tap it, and it's attacking that player. + this.addAbility(new AttacksTriggeredAbility( + new MidnightCrusaderShuttleEffect(), + false, null, SetTargetPointer.PLAYER + ).withFlavorWord("Midnight Entity")); + + // Crew 2 + this.addAbility(new CrewAbility(2)); + } + + private MidnightCrusaderShuttle(final MidnightCrusaderShuttle card) { + super(card); + } + + @Override + public MidnightCrusaderShuttle copy() { + return new MidnightCrusaderShuttle(this); + } +} + +class MidnightCrusaderShuttleEffect extends OneShotEffect { + + private static final FaceVillainousChoice choice = new FaceVillainousChoice( + Outcome.Sacrifice, + new MidnightCrusaderShuttleFirstChoice(), + new MidnightCrusaderShuttleSecondChoice() + ); + + MidnightCrusaderShuttleEffect() { + super(Outcome.Benefit); + staticText = "defending player " + choice.generateRule(); + } + + private MidnightCrusaderShuttleEffect(final MidnightCrusaderShuttleEffect effect) { + super(effect); + } + + @Override + public MidnightCrusaderShuttleEffect copy() { + return new MidnightCrusaderShuttleEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Optional.ofNullable(getTargetPointer().getFirst(game, source)) + .map(game::getPlayer) + .ifPresent(player -> choice.faceChoice(player, game, source)); + return true; + } +} + +class MidnightCrusaderShuttleFirstChoice extends VillainousChoice { + + MidnightCrusaderShuttleFirstChoice() { + super("That player sacrifices a creature of their choice", "Sacrifice a creature"); + } + + @Override + public boolean doChoice(Player player, Game game, Ability source) { + return new SacrificeTargetCost(StaticFilters.FILTER_PERMANENT_CREATURE) + .pay(source, game, source, player.getId(), true); + } +} + +class MidnightCrusaderShuttleSecondChoice extends VillainousChoice { + + MidnightCrusaderShuttleSecondChoice() { + super( + "you gain control of a creature of your choice that player controls until end of turn. " + + "If you gain control of a creature this way, tap it, and it's attacking that player", + "{controller} gains control of one of your creatures and it attacks you" + ); + } + + @Override + public boolean doChoice(Player player, Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null || !game.getBattlefield().contains( + StaticFilters.FILTER_CONTROLLED_CREATURE, + player.getId(), source, game, 1 + )) { + return false; + } + FilterPermanent filter = new FilterCreaturePermanent("creature defending player controls"); + filter.add(new ControllerIdPredicate(player.getId())); + TargetPermanent target = new TargetPermanent(filter); + target.withNotTarget(true); + controller.choose(Outcome.GainControl, target, source, game); + Permanent permanent = game.getPermanent(source.getSourceId()); + if (permanent == null) { + return false; + } + game.addEffect(new GainControlTargetEffect(Duration.EndOfTurn, true) + .setTargetPointer(new FixedTarget(permanent, game)), source); + game.processAction(); + if (!permanent.isControlledBy(controller.getId())) { + return false; + } + permanent.tap(source, game); + game.getCombat().addAttackingCreature(permanent.getId(), game, player.getId()); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/m/MillicentRestlessRevenant.java b/Mage.Sets/src/mage/cards/m/MillicentRestlessRevenant.java index a69602dea49..7d46e8984dc 100644 --- a/Mage.Sets/src/mage/cards/m/MillicentRestlessRevenant.java +++ b/Mage.Sets/src/mage/cards/m/MillicentRestlessRevenant.java @@ -4,10 +4,9 @@ import mage.MageInt; import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.AffinityEffect; import mage.abilities.effects.common.CreateTokenEffect; -import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; import mage.abilities.hint.Hint; import mage.abilities.hint.ValueHint; import mage.abilities.keyword.FlyingAbility; @@ -34,9 +33,8 @@ import java.util.UUID; */ public final class MillicentRestlessRevenant extends CardImpl { - private static final FilterPermanent filter = new FilterControlledPermanent(SubType.SPIRIT); - private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter); - private static final Hint hint = new ValueHint("Spirits you control", xValue); + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.SPIRIT, "Spirits"); + private static final Hint hint = new ValueHint("Spirits you control", new PermanentsOnBattlefieldCount(filter)); public MillicentRestlessRevenant(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{W}{U}"); @@ -48,9 +46,7 @@ public final class MillicentRestlessRevenant extends CardImpl { this.toughness = new MageInt(4); // This spell costs {1} less to cast for each Spirit you control. - this.addAbility(new SimpleStaticAbility( - Zone.ALL, new SpellCostReductionForEachSourceEffect(1, xValue) - ).addHint(hint)); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new AffinityEffect(filter)).addHint(hint)); // Flying this.addAbility(FlyingAbility.getInstance()); diff --git a/Mage.Sets/src/mage/cards/m/MinionOfTheWastes.java b/Mage.Sets/src/mage/cards/m/MinionOfTheWastes.java index d0d0188faca..0d3dbdf0a3c 100644 --- a/Mage.Sets/src/mage/cards/m/MinionOfTheWastes.java +++ b/Mage.Sets/src/mage/cards/m/MinionOfTheWastes.java @@ -87,7 +87,7 @@ class MinionOfTheWastesEffect extends ReplacementEffectImpl { if (creature == null || controller == null) { return false; } - int payAmount = controller.getAmount(0, controller.getLife(), "Pay any amount of life", game); + int payAmount = controller.getAmount(0, controller.getLife(), "Pay any amount of life", source, game); Cost cost = new PayLifeCost(payAmount); if (!cost.pay(source, game, source, source.getControllerId(), true)) { return false; diff --git a/Mage.Sets/src/mage/cards/m/MishraTamerOfMakFawa.java b/Mage.Sets/src/mage/cards/m/MishraTamerOfMakFawa.java index 8a5884dbd94..52719912418 100644 --- a/Mage.Sets/src/mage/cards/m/MishraTamerOfMakFawa.java +++ b/Mage.Sets/src/mage/cards/m/MishraTamerOfMakFawa.java @@ -35,9 +35,8 @@ public final class MishraTamerOfMakFawa extends CardImpl { // Permanents you control have "Ward--Sacrifice a permanent." this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect( - new WardAbility(new SacrificeTargetCost( - StaticFilters.FILTER_PERMANENT - ), false), Duration.WhileOnBattlefield + new WardAbility(new SacrificeTargetCost(StaticFilters.FILTER_PERMANENT), false), + Duration.WhileOnBattlefield, StaticFilters.FILTER_PERMANENTS ).withForceQuotes())); // Each artifact card in your graveyard has unearth {1}{B}{R} diff --git a/Mage.Sets/src/mage/cards/m/MobVerdict.java b/Mage.Sets/src/mage/cards/m/MobVerdict.java new file mode 100644 index 00000000000..b7a02894af2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MobVerdict.java @@ -0,0 +1,132 @@ +package mage.cards.m; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.choices.VoteHandler; +import mage.constants.AbilityWord; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.TargetController; +import mage.filter.FilterPlayer; +import mage.filter.StaticFilters; +import mage.game.Controllable; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPlayer; + +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * @author TheElk801 + */ +public final class MobVerdict extends CardImpl { + + public MobVerdict(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{R}{R}"); + + // Secret council -- Each player secretly votes for another player, then those votes are revealed. For each vote an opponent received, Mob Verdict deals 2 damage to that player and each creature that player controls. For each vote you received, draw a card. + this.getSpellAbility().addEffect(new MobVerdictEffect()); + this.getSpellAbility().setAbilityWord(AbilityWord.SECRET_COUNCIL); + } + + private MobVerdict(final MobVerdict card) { + super(card); + } + + @Override + public MobVerdict copy() { + return new MobVerdict(this); + } +} + +class MobVerdictEffect extends OneShotEffect { + + MobVerdictEffect() { + super(Outcome.Benefit); + staticText = "each player secretly votes for another player, then those votes are revealed. " + + "For each vote an opponent received, {this} deals 2 damage to that " + + "player and each creature that player controls. For each vote you received, draw a card"; + } + + private MobVerdictEffect(final MobVerdictEffect effect) { + super(effect); + } + + @Override + public MobVerdictEffect copy() { + return new MobVerdictEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + MobVerdictVote vote = new MobVerdictVote(); + vote.doVotes(source, game); + for (UUID opponentId : game.getOpponents(source.getControllerId())) { + Player opponent = game.getPlayer(opponentId); + if (opponent == null) { + continue; + } + int count = vote.getVoteCount(opponent); + if (count < 1) { + continue; + } + opponent.damage(2 * count, source, game); + for (Permanent permanent : game.getBattlefield().getActivePermanents( + StaticFilters.FILTER_CONTROLLED_CREATURE, opponentId, source, game + )) { + permanent.damage(2 * count, source, game); + } + } + Optional.ofNullable(source) + .map(Controllable::getControllerId) + .map(game::getPlayer) + .ifPresent(player -> player.drawCards(vote.getVoteCount(player), source, game)); + return true; + } +} + +class MobVerdictVote extends VoteHandler { + + private static final FilterPlayer filter = new FilterPlayer("another player"); + + static { + filter.add(TargetController.NOT_YOU.getPlayerPredicate()); + } + + MobVerdictVote() { + super(); + this.secret = true; + } + + @Override + protected Set getPossibleVotes(Ability source, Game game) { + return game + .getState() + .getPlayersInRange(source.getControllerId(), game) + .stream() + .map(game::getPlayer) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + } + + @Override + protected Player playerChoose(String voteInfo, Player player, Player decidingPlayer, Ability source, Game game) { + TargetPlayer target = new TargetPlayer(filter); + target.withNotTarget(true); + target.withChooseHint("to vote for"); + decidingPlayer.choose(Outcome.Benefit, target, source, game); + return game.getPlayer(target.getFirstTarget()); + } + + @Override + protected String voteName(Player vote) { + return vote.getName(); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MoltenExhale.java b/Mage.Sets/src/mage/cards/m/MoltenExhale.java index b70d0407772..723885c7f30 100644 --- a/Mage.Sets/src/mage/cards/m/MoltenExhale.java +++ b/Mage.Sets/src/mage/cards/m/MoltenExhale.java @@ -1,11 +1,17 @@ package mage.cards.m; +import mage.abilities.Ability; import mage.abilities.common.PayMoreToCastAsThoughtItHadFlashAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.CostImpl; +import mage.abilities.costs.CostsImpl; import mage.abilities.costs.common.BeholdDragonCost; +import mage.abilities.effects.Effect; import mage.abilities.effects.common.DamageTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.target.Target; import mage.target.common.TargetCreatureOrPlaneswalker; import java.util.UUID; @@ -19,14 +25,18 @@ public final class MoltenExhale extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{R}"); // You may cast this spell as though it had flash if you behold a Dragon as an additional cost to cast it. - this.addAbility(new PayMoreToCastAsThoughtItHadFlashAbility( - this, new BeholdDragonCost(), "you may cast this spell as though " + - "it had flash if you behold a Dragon as an additional cost to cast it" - )); + Ability ability = new PayMoreToCastAsThoughtItHadFlashAbility(this, new BeholdDragonCost(), + "you may cast this spell as though it had flash if you behold a Dragon as an additional cost to cast it."); + Target target = new TargetCreatureOrPlaneswalker(); + Effect effect = new DamageTargetEffect(4); + ability.addEffect(effect); + ability.addTarget(target); + ability.setAdditionalCostsRuleVisible(false); + this.addAbility(ability); // Molten Exhale deals 4 damage to target creature or planeswalker. - this.getSpellAbility().addEffect(new DamageTargetEffect(4)); - this.getSpellAbility().addTarget(new TargetCreatureOrPlaneswalker()); + this.getSpellAbility().addEffect(effect); + this.getSpellAbility().addTarget(target); } private MoltenExhale(final MoltenExhale card) { diff --git a/Mage.Sets/src/mage/cards/m/MonasteryRaid.java b/Mage.Sets/src/mage/cards/m/MonasteryRaid.java new file mode 100644 index 00000000000..10f4216c7fd --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MonasteryRaid.java @@ -0,0 +1,56 @@ +package mage.cards.m; + +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.dynamicvalue.common.GetXValue; +import mage.abilities.effects.common.ExileTopXMayPlayUntilEffect; +import mage.abilities.keyword.FreerunningAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.game.Game; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MonasteryRaid extends CardImpl { + + public MonasteryRaid(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{R}"); + + // Freerunning {X}{R} + this.addAbility(new FreerunningAbility("{X}{R}")); + + // Exile the top two cards of your library. If this spell's freerunning cost was paid, exile the top X cards of your library instead. You may play the exiled cards until the end of your next turn. + this.getSpellAbility().addEffect(new ConditionalOneShotEffect( + new ExileTopXMayPlayUntilEffect(GetXValue.instance, false, Duration.UntilEndOfYourNextTurn), + new ExileTopXMayPlayUntilEffect(2, Duration.UntilEndOfYourNextTurn), + MonasteryRaidCondition.instance, "exile the top two cards of your library. " + + "If this spell's freerunning cost was paid, exile the top X cards of your library instead. " + + "You may play the exiled cards until the end of your next turn" + )); + } + + private MonasteryRaid(final MonasteryRaid card) { + super(card); + } + + @Override + public MonasteryRaid copy() { + return new MonasteryRaid(this); + } +} + +enum MonasteryRaidCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + return CardUtil.checkSourceCostsTagExists(game, source, FreerunningAbility.getActivationKey()); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MooglesValor.java b/Mage.Sets/src/mage/cards/m/MooglesValor.java new file mode 100644 index 00000000000..ed82d21e02b --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MooglesValor.java @@ -0,0 +1,42 @@ +package mage.cards.m; + +import mage.abilities.dynamicvalue.common.CreaturesYouControlCount; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.IndestructibleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.filter.StaticFilters; +import mage.game.permanent.token.MoogleToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MooglesValor extends CardImpl { + + public MooglesValor(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{W}{W}"); + + // For each creature you control, create a 1/2 white Moogle creature token with lifelink. Then creatures you control gain indestructible until end of turn. + this.getSpellAbility().addEffect(new CreateTokenEffect( + new MoogleToken(), CreaturesYouControlCount.instance + ).setText("for each creature you control, create a 1/2 white Moogle creature token with lifelink")); + this.getSpellAbility().addEffect(new GainAbilityControlledEffect( + IndestructibleAbility.getInstance(), Duration.EndOfTurn, + StaticFilters.FILTER_PERMANENT_CREATURES + ).concatBy("Then")); + } + + private MooglesValor(final MooglesValor card) { + super(card); + } + + @Override + public MooglesValor copy() { + return new MooglesValor(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MosswoodDreadknight.java b/Mage.Sets/src/mage/cards/m/MosswoodDreadknight.java index 64f2703bb4f..b8039f85fef 100644 --- a/Mage.Sets/src/mage/cards/m/MosswoodDreadknight.java +++ b/Mage.Sets/src/mage/cards/m/MosswoodDreadknight.java @@ -1,17 +1,15 @@ package mage.cards.m; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.DiesSourceTriggeredAbility; -import mage.abilities.effects.AsThoughEffectImpl; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.LoseLifeSourceControllerEffect; +import mage.abilities.effects.common.asthought.MayCastFromGraveyardAsAdventureEffect; import mage.abilities.keyword.TrampleAbility; import mage.cards.AdventureCard; -import mage.cards.Card; import mage.cards.CardSetInfo; -import mage.constants.*; -import mage.game.Game; +import mage.constants.CardType; +import mage.constants.SubType; import java.util.UUID; @@ -33,7 +31,7 @@ public final class MosswoodDreadknight extends AdventureCard { this.addAbility(TrampleAbility.getInstance()); // When Mosswood Dreadknight dies, you may cast it from your graveyard as an Adventure until the end of your next turn. - this.addAbility(new DiesSourceTriggeredAbility(new MosswoodDreadknightEffect())); + this.addAbility(new DiesSourceTriggeredAbility(new MayCastFromGraveyardAsAdventureEffect())); // Dread Whispers // You draw a card and you lose 1 life. @@ -51,43 +49,4 @@ public final class MosswoodDreadknight extends AdventureCard { public MosswoodDreadknight copy() { return new MosswoodDreadknight(this); } -} - -class MosswoodDreadknightEffect extends AsThoughEffectImpl { - - MosswoodDreadknightEffect() { - super(AsThoughEffectType.CAST_ADVENTURE_FROM_NOT_OWN_HAND_ZONE, Duration.UntilEndOfYourNextTurn, Outcome.Benefit); - staticText = "you may cast it from your graveyard as an Adventure until the end of your next turn"; - } - - private MosswoodDreadknightEffect(final MosswoodDreadknightEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - return true; - } - - @Override - public MosswoodDreadknightEffect copy() { - return new MosswoodDreadknightEffect(this); - } - - @Override - public boolean applies(UUID sourceId, Ability source, UUID affectedControllerId, Game game) { - if (!source.isControlledBy(affectedControllerId)) { - return false; - } - Card card = game.getCard(sourceId); - if (card == null || card.getMainCard() == null || !card.getMainCard().getId().equals(source.getSourceId())) { - return false; - } - - Card sourceCard = game.getCard(source.getSourceId()); - - return sourceCard != null - && game.getState().getZone(source.getSourceId()) == Zone.GRAVEYARD - && source.getSourceObjectZoneChangeCounter() == sourceCard.getZoneChangeCounter(game); - } -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/n/NamazuTrader.java b/Mage.Sets/src/mage/cards/n/NamazuTrader.java new file mode 100644 index 00000000000..db6873a3b77 --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/NamazuTrader.java @@ -0,0 +1,54 @@ +package mage.cards.n; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.LoseLifeSourceControllerEffect; +import mage.abilities.effects.keyword.SurveilEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.game.permanent.token.TreasureToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class NamazuTrader extends CardImpl { + + public NamazuTrader(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}"); + + this.subtype.add(SubType.FISH); + this.subtype.add(SubType.CITIZEN); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // When this creature enters, you lose 1 life and create a Treasure token. + Ability ability = new EntersBattlefieldTriggeredAbility(new LoseLifeSourceControllerEffect(1)); + ability.addEffect(new CreateTokenEffect(new TreasureToken()).concatBy("and")); + this.addAbility(ability); + + // Whenever this creature attacks, you may sacrifice another creature or artifact. If you do, surveil 2. + this.addAbility(new AttacksTriggeredAbility(new DoIfCostPaid( + new SurveilEffect(2), + new SacrificeTargetCost(StaticFilters.FILTER_CONTROLLED_ANOTHER_CREATURE_OR_ARTIFACT) + ))); + } + + private NamazuTrader(final NamazuTrader card) { + super(card); + } + + @Override + public NamazuTrader copy() { + return new NamazuTrader(this); + } +} diff --git a/Mage.Sets/src/mage/cards/n/NamelessRace.java b/Mage.Sets/src/mage/cards/n/NamelessRace.java index 8f92effeaf1..e5be245a8b1 100644 --- a/Mage.Sets/src/mage/cards/n/NamelessRace.java +++ b/Mage.Sets/src/mage/cards/n/NamelessRace.java @@ -111,7 +111,7 @@ class NamelessRaceEffect extends ReplacementEffectImpl { int permanentsInPlay = new PermanentsOnBattlefieldCount(filter).calculate(game, source, null); int cardsInGraveyards = new CardsInAllGraveyardsCount(filter2).calculate(game, source, null); int maxAmount = Math.min(permanentsInPlay + cardsInGraveyards, controller.getLife()); - int payAmount = controller.getAmount(0, maxAmount, "Pay up to " + maxAmount + " life", game); + int payAmount = controller.getAmount(0, maxAmount, "Pay up to " + maxAmount + " life", source, game); Cost cost = new PayLifeCost(payAmount); if (!cost.pay(source, game, source, source.getControllerId(), true)) { return false; diff --git a/Mage.Sets/src/mage/cards/n/NarnamRenegade.java b/Mage.Sets/src/mage/cards/n/NarnamRenegade.java index 670673c5f49..a66b114f50c 100644 --- a/Mage.Sets/src/mage/cards/n/NarnamRenegade.java +++ b/Mage.Sets/src/mage/cards/n/NarnamRenegade.java @@ -7,6 +7,7 @@ import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.keyword.DeathtouchAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.constants.AbilityWord; import mage.constants.CardType; import mage.constants.SubType; import mage.counters.CounterType; @@ -33,9 +34,9 @@ public final class NarnamRenegade extends CardImpl { // Revolt — Narnam Renegade enters the battlefield with a +1/+1 counter on it if a permanent you controlled left this battlefield this turn. this.addAbility(new EntersBattlefieldAbility( new AddCountersSourceEffect(CounterType.P1P1.createInstance()), false, - RevoltCondition.instance, "Revolt — {this} enters with " + - "a +1/+1 counter on it if a permanent you controlled left the battlefield this turn.", null - ).addHint(RevoltCondition.getHint()), new RevoltWatcher()); + RevoltCondition.instance, "{this} enters with a +1/+1 counter on it " + + "if a permanent you controlled left the battlefield this turn.", null + ).setAbilityWord(AbilityWord.REVOLT).addHint(RevoltCondition.getHint()), new RevoltWatcher()); } private NarnamRenegade(final NarnamRenegade card) { diff --git a/Mage.Sets/src/mage/cards/n/Necrodominance.java b/Mage.Sets/src/mage/cards/n/Necrodominance.java index bd172ea7801..d1551da799c 100644 --- a/Mage.Sets/src/mage/cards/n/Necrodominance.java +++ b/Mage.Sets/src/mage/cards/n/Necrodominance.java @@ -81,7 +81,7 @@ class NecrodominanceEffect extends OneShotEffect { if (controller == null) { return false; } - int payAmount = controller.getAmount(0, controller.getLife(), "Pay any amount of life", game); + int payAmount = controller.getAmount(0, controller.getLife(), "Pay any amount of life", source, game); Cost cost = new PayLifeCost(payAmount); if (!cost.pay(source, game, source, source.getControllerId(), true)) { return false; diff --git a/Mage.Sets/src/mage/cards/n/Nethergoyf.java b/Mage.Sets/src/mage/cards/n/Nethergoyf.java index bc8d5c71602..858e2718695 100644 --- a/Mage.Sets/src/mage/cards/n/Nethergoyf.java +++ b/Mage.Sets/src/mage/cards/n/Nethergoyf.java @@ -6,7 +6,6 @@ import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.Cost; import mage.abilities.costs.CostsImpl; import mage.abilities.costs.common.ExileFromGraveCost; -import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.CardTypesInGraveyardCount; import mage.abilities.effects.common.continuous.SetBasePowerToughnessPlusOneSourceEffect; import mage.abilities.hint.HintUtils; @@ -100,7 +99,11 @@ class NethergoyfTarget extends TargetCardInYourGraveyard { types.size() + " of 4", types.size() >= 4 ? Color.GREEN : Color.RED ); - text += " [" + types.stream().map(CardType::toString).collect(Collectors.joining(", ")) + "])"; + String info = types.stream().map(CardType::toString).collect(Collectors.joining(", ")); + if (!info.isEmpty()) { + text += " [" + info + "]"; + } + text += ")"; return text; } diff --git a/Mage.Sets/src/mage/cards/n/NibelheimAflame.java b/Mage.Sets/src/mage/cards/n/NibelheimAflame.java new file mode 100644 index 00000000000..43362e5b1fc --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/NibelheimAflame.java @@ -0,0 +1,85 @@ +package mage.cards.n; + +import mage.abilities.Ability; +import mage.abilities.condition.common.CastFromGraveyardSourceCondition; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.discard.DiscardHandControllerEffect; +import mage.abilities.keyword.FlashbackAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class NibelheimAflame extends CardImpl { + + public NibelheimAflame(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{R}{R}"); + + // Choose target creature you control. It deals damage equal to its power to each other creature. If this spell was cast from a graveyard, discard your hand and draw four cards. + this.getSpellAbility().addEffect(new NibelheimAflameEffect()); + this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent()); + this.getSpellAbility().addEffect(new ConditionalOneShotEffect( + new DiscardHandControllerEffect(), CastFromGraveyardSourceCondition.instance, + "If this spell was cast from a graveyard, discard your hand and draw four cards" + ).addEffect(new DrawCardSourceControllerEffect(4))); + + // Flashback {5}{R}{R} + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{5}{R}{R}"))); + } + + private NibelheimAflame(final NibelheimAflame card) { + super(card); + } + + @Override + public NibelheimAflame copy() { + return new NibelheimAflame(this); + } +} + +class NibelheimAflameEffect extends OneShotEffect { + + NibelheimAflameEffect() { + super(Outcome.Benefit); + staticText = "choose target creature you control. It deals damage equal to its power to each other creature"; + } + + private NibelheimAflameEffect(final NibelheimAflameEffect effect) { + super(effect); + } + + @Override + public NibelheimAflameEffect copy() { + return new NibelheimAflameEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent == null) { + return false; + } + int power = permanent.getPower().getValue(); + if (power < 1) { + return false; + } + for (Permanent creature : game.getBattlefield().getActivePermanents( + StaticFilters.FILTER_ANOTHER_CREATURE, source.getControllerId(), source, game + )) { + creature.damage(power, permanent.getId(), source, game); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/n/NightMarketAeronaut.java b/Mage.Sets/src/mage/cards/n/NightMarketAeronaut.java index 0f13cadb050..a6b8a0ec9ef 100644 --- a/Mage.Sets/src/mage/cards/n/NightMarketAeronaut.java +++ b/Mage.Sets/src/mage/cards/n/NightMarketAeronaut.java @@ -7,6 +7,7 @@ import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.constants.AbilityWord; import mage.constants.CardType; import mage.constants.SubType; import mage.counters.CounterType; @@ -34,9 +35,9 @@ public final class NightMarketAeronaut extends CardImpl { // a permanent you controlled left the battlefield this turn. this.addAbility(new EntersBattlefieldAbility( new AddCountersSourceEffect(CounterType.P1P1.createInstance()), false, - RevoltCondition.instance, "Revolt — {this} enters with " + - "a +1/+1 counter on it if a permanent you controlled left the battlefield this turn.", null - ).addHint(RevoltCondition.getHint()), new RevoltWatcher()); + RevoltCondition.instance, "{this} enters with a +1/+1 counter on it " + + "if a permanent you controlled left the battlefield this turn.", null + ).setAbilityWord(AbilityWord.REVOLT).addHint(RevoltCondition.getHint()), new RevoltWatcher()); } private NightMarketAeronaut(final NightMarketAeronaut card) { diff --git a/Mage.Sets/src/mage/cards/n/NightshadeAssassin.java b/Mage.Sets/src/mage/cards/n/NightshadeAssassin.java index e79a9bbf471..e6d5dee8b26 100644 --- a/Mage.Sets/src/mage/cards/n/NightshadeAssassin.java +++ b/Mage.Sets/src/mage/cards/n/NightshadeAssassin.java @@ -88,7 +88,7 @@ class NightshadeAssassinEffect extends OneShotEffect { FilterCard filter = new FilterCard(); filter.add(new ColorPredicate(ObjectColor.BLACK)); int blackCards = controller.getHand().count(filter, source.getControllerId(), source, game); - int cardsToReveal = controller.getAmount(0, blackCards, "Reveal how many black cards?", game); + int cardsToReveal = controller.getAmount(0, blackCards, "Reveal how many black cards?", source, game); game.informPlayers(controller.getLogName() + " chooses to reveal " + cardsToReveal + " black cards."); if (cardsToReveal > 0) { TargetCardInHand target = new TargetCardInHand(cardsToReveal, cardsToReveal, filter); diff --git a/Mage.Sets/src/mage/cards/n/NumaJoragaChieftain.java b/Mage.Sets/src/mage/cards/n/NumaJoragaChieftain.java index 04c6abef386..003c8eaae29 100644 --- a/Mage.Sets/src/mage/cards/n/NumaJoragaChieftain.java +++ b/Mage.Sets/src/mage/cards/n/NumaJoragaChieftain.java @@ -81,7 +81,8 @@ class NumaJoragaChieftainEffect extends OneShotEffect { if (!player.chooseUse(Outcome.BoostCreature, "Pay " + cost.getText() + "?", source, game)) { return false; } - int costX = player.announceXMana(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source); + // TODO: add some AI hints by min/max values + int costX = player.announceX(0, Integer.MAX_VALUE, "Announce the value for {X} (pay to distribute counters)", game, source,true); cost.add(new GenericManaCost(2 * costX)); if (!cost.pay(source, game, source, source.getControllerId(), false, null)) { return false; diff --git a/Mage.Sets/src/mage/cards/n/NyssaOfTraken.java b/Mage.Sets/src/mage/cards/n/NyssaOfTraken.java new file mode 100644 index 00000000000..deb46368105 --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/NyssaOfTraken.java @@ -0,0 +1,105 @@ +package mage.cards.n; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.TapTargetEffect; +import mage.abilities.effects.common.continuous.MaximumHandSizeControllerEffect; +import mage.abilities.keyword.DoctorsCompanionAbility; +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.common.TargetCreaturePermanent; +import mage.target.common.TargetSacrifice; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class NyssaOfTraken extends CardImpl { + + public NyssaOfTraken(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SCIENTIST); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // You have no maximum hand size. + this.addAbility(new SimpleStaticAbility(new MaximumHandSizeControllerEffect( + Integer.MAX_VALUE, Duration.WhileOnBattlefield, + MaximumHandSizeControllerEffect.HandSizeModification.SET + ))); + + // Sonic Booster -- Whenever Nyssa of Traken attacks, sacrifice any number of artifacts. When you sacrifice one or more artifacts this way, tap up to that many target creatures and draw that many cards. + this.addAbility(new AttacksTriggeredAbility(new NyssaOfTrakenEffect()).withFlavorWord("Sonic Booster")); + + // Doctor's companion + this.addAbility(DoctorsCompanionAbility.getInstance()); + } + + private NyssaOfTraken(final NyssaOfTraken card) { + super(card); + } + + @Override + public NyssaOfTraken copy() { + return new NyssaOfTraken(this); + } +} + +class NyssaOfTrakenEffect extends OneShotEffect { + + NyssaOfTrakenEffect() { + super(Outcome.Benefit); + staticText = "sacrifice any number of artifacts. When you sacrifice one or more artifacts this way, " + + "tap up to that many target creatures and draw that many cards."; + } + + private NyssaOfTrakenEffect(final NyssaOfTrakenEffect effect) { + super(effect); + } + + @Override + public NyssaOfTrakenEffect copy() { + return new NyssaOfTrakenEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + TargetSacrifice target = new TargetSacrifice( + 0, Integer.MAX_VALUE, StaticFilters.FILTER_PERMANENT_ARTIFACTS + ); + player.choose(outcome, target, source, game); + int count = 0; + for (UUID targetId : target.getTargets()) { + Permanent permanent = game.getPermanent(targetId); + if (permanent != null && permanent.sacrifice(source, game)) { + count++; + } + } + if (count < 1) { + return false; + } + ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility(new TapTargetEffect(), false); + ability.addEffect(new DrawCardSourceControllerEffect(count).concatBy("and")); + ability.addTarget(new TargetCreaturePermanent(0, count)); + game.fireReflexiveTriggeredAbility(ability, source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/o/OakaTravelingMerchant.java b/Mage.Sets/src/mage/cards/o/OakaTravelingMerchant.java new file mode 100644 index 00000000000..50454bd54ea --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OakaTravelingMerchant.java @@ -0,0 +1,47 @@ +package mage.cards.o; + +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.constants.SuperType; +import mage.filter.StaticFilters; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class OakaTravelingMerchant extends CardImpl { + + public OakaTravelingMerchant(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.CITIZEN); + this.power = new MageInt(1); + this.toughness = new MageInt(2); + + // {T}, Remove a counter from a nonland permanent you control: Draw a card. + Ability ability = new SimpleActivatedAbility(new DrawCardSourceControllerEffect(1), new TapSourceCost()); + ability.addCost(new RemoveCounterCost(new TargetPermanent(StaticFilters.FILTER_CONTROLLED_PERMANENT_NON_LAND))); + this.addAbility(ability); + } + + private OakaTravelingMerchant(final OakaTravelingMerchant card) { + super(card); + } + + @Override + public OakaTravelingMerchant copy() { + return new OakaTravelingMerchant(this); + } +} diff --git a/Mage.Sets/src/mage/cards/o/ObservedStasis.java b/Mage.Sets/src/mage/cards/o/ObservedStasis.java new file mode 100644 index 00000000000..e7cbc2b01a0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/ObservedStasis.java @@ -0,0 +1,109 @@ +package mage.cards.o; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.combat.CantAttackBlockAttachedEffect; +import mage.abilities.effects.common.continuous.LoseAllAbilitiesAttachedEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.abilities.keyword.FlashAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AttachmentType; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.permanent.TappedPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ObservedStasis extends CardImpl { + + public ObservedStasis(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{U}"); + + this.subtype.add(SubType.AURA); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // Enchant creature an opponent controls + TargetPermanent auraTarget = new TargetPermanent(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + this.addAbility(new EnchantAbility(auraTarget)); + + // When this Aura enters, remove enchanted creature from combat. Then draw a card for each tapped creature its controller controls. + this.addAbility(new EntersBattlefieldTriggeredAbility(new ObservedStasisEffect())); + + // Enchanted creature loses all abilities and can't attack or block. + Ability ability = new SimpleStaticAbility(new LoseAllAbilitiesAttachedEffect(AttachmentType.AURA)); + ability.addEffect(new CantAttackBlockAttachedEffect(AttachmentType.AURA).setText("and can't attack or block")); + this.addAbility(ability); + } + + private ObservedStasis(final ObservedStasis card) { + super(card); + } + + @Override + public ObservedStasis copy() { + return new ObservedStasis(this); + } +} + +class ObservedStasisEffect extends OneShotEffect { + + private static final FilterPermanent filter = new FilterControlledCreaturePermanent(); + + static { + filter.add(TappedPredicate.TAPPED); + } + + ObservedStasisEffect() { + super(Outcome.Benefit); + staticText = "remove enchanted creature from combat. " + + "Then draw a card for each tapped creature its controller controls"; + } + + private ObservedStasisEffect(final ObservedStasisEffect effect) { + super(effect); + } + + @Override + public ObservedStasisEffect copy() { + return new ObservedStasisEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = Optional + .ofNullable(source.getSourcePermanentOrLKI(game)) + .map(Permanent::getAttachedTo) + .map(game::getPermanent) + .orElse(null); + if (permanent == null) { + return false; + } + game.getCombat().removeAttacker(permanent.getId(), game); + int count = game.getBattlefield().count(filter, permanent.getControllerId(), source, game); + if (count > 0) { + Optional.ofNullable(source.getControllerId()) + .map(game::getPlayer) + .ifPresent(player -> player.drawCards(count, source, game)); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/o/OctopusUmbra.java b/Mage.Sets/src/mage/cards/o/OctopusUmbra.java index 44c1874ff41..14dbf1bc87f 100644 --- a/Mage.Sets/src/mage/cards/o/OctopusUmbra.java +++ b/Mage.Sets/src/mage/cards/o/OctopusUmbra.java @@ -9,7 +9,7 @@ import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.AttachEffect; import mage.abilities.effects.common.TapTargetEffect; import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; -import mage.abilities.effects.common.continuous.SetBasePowerToughnessEnchantedEffect; +import mage.abilities.effects.common.continuous.SetBasePowerToughnessAttachedEffect; import mage.constants.Outcome; import mage.target.TargetPermanent; import mage.abilities.keyword.EnchantAbility; @@ -19,7 +19,6 @@ import mage.cards.CardSetInfo; import mage.constants.AttachmentType; import mage.constants.CardType; import mage.constants.ComparisonType; -import mage.constants.Zone; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.mageobject.PowerPredicate; @@ -51,7 +50,7 @@ public final class OctopusUmbra extends CardImpl { Ability abilityToAdd = new AttacksTriggeredAbility(new TapTargetEffect(), true); abilityToAdd.addTarget(new TargetCreaturePermanent(filter)); ability = new SimpleStaticAbility( - new SetBasePowerToughnessEnchantedEffect(8, 8) + new SetBasePowerToughnessAttachedEffect(8, 8, AttachmentType.AURA) ); ability.addEffect(new GainAbilityAttachedEffect( abilityToAdd, AttachmentType.AURA diff --git a/Mage.Sets/src/mage/cards/o/OildeepGearhulk.java b/Mage.Sets/src/mage/cards/o/OildeepGearhulk.java index 8a6d151bf83..ad63fcbc14e 100644 --- a/Mage.Sets/src/mage/cards/o/OildeepGearhulk.java +++ b/Mage.Sets/src/mage/cards/o/OildeepGearhulk.java @@ -1,39 +1,35 @@ package mage.cards.o; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.DrawDiscardTargetEffect; -import mage.abilities.effects.common.LookAtTargetPlayerHandEffect; -import mage.abilities.effects.common.discard.DiscardAndDrawThatManyEffect; -import mage.cards.Card; -import mage.constants.Outcome; -import mage.constants.SubType; -import mage.abilities.keyword.LifelinkAbility; import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.LifelinkAbility; import mage.abilities.keyword.WardAbility; +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.filter.FilterCard; -import mage.filter.StaticFilters; import mage.game.Game; import mage.players.Player; import mage.target.TargetCard; import mage.target.TargetPlayer; import mage.target.common.TargetCardInHand; +import java.util.UUID; + /** - * * @author Jmlundeen */ public final class OildeepGearhulk extends CardImpl { public OildeepGearhulk(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{U}{U}{B}{B}"); - + this.subtype.add(SubType.CONSTRUCT); this.power = new MageInt(4); this.toughness = new MageInt(4); @@ -86,12 +82,15 @@ class OildeepGearhulkEffect extends OneShotEffect { controller.lookAtCards(targetPlayer.getName() + " Hand", targetPlayer.getHand(), game); TargetCard chosenCard = new TargetCardInHand(0, 1, new FilterCard("card to discard")); - if (controller.choose(Outcome.Discard, targetPlayer.getHand(), chosenCard, source, game)) { - Card card = game.getCard(chosenCard.getFirstTarget()); - targetPlayer.discard(card, false, source, game); - targetPlayer.drawCards(1, source, game); - return true; + if (!controller.choose(Outcome.Discard, targetPlayer.getHand(), chosenCard, source, game)) { + return false; } - return false; + Card card = game.getCard(chosenCard.getFirstTarget()); + if (card == null) { + return false; + } + targetPlayer.discard(card, false, source, game); + targetPlayer.drawCards(1, source, game); + return true; } } diff --git a/Mage.Sets/src/mage/cards/o/OverclockedElectromancer.java b/Mage.Sets/src/mage/cards/o/OverclockedElectromancer.java new file mode 100644 index 00000000000..f382df3bfd0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OverclockedElectromancer.java @@ -0,0 +1,154 @@ +package mage.cards.o; + +import mage.MageInt; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.DealsDamageToACreatureTriggeredAbility; +import mage.abilities.costs.common.PayEnergyCost; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.SourcePermanentPowerValue; +import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.effects.common.counter.GetEnergyCountersControllerEffect; +import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.WatcherScope; +import mage.counters.CounterType; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicate; +import mage.game.Game; +import mage.game.events.DamagedPermanentEvent; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.watchers.Watcher; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class OverclockedElectromancer extends CardImpl { + + private static final FilterCreaturePermanent filter + = new FilterCreaturePermanent("creature, if that creature was dealt excess damage this turn"); + + static { + filter.add(OverclockedElectromancerPredicate.instance); + } + + public OverclockedElectromancer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); + + this.subtype.add(SubType.LIZARD); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // At the beginning of combat on your turn, you may pay {E}{E}{E}. If you do, put a +1/+1 counter on Overclocked Electromancer. + this.addAbility(new BeginningOfCombatTriggeredAbility(new DoIfCostPaid( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), new PayEnergyCost(3) + ))); + + // Whenever Overclocked Electromancer attacks, double its power until end of turn. + this.addAbility(new AttacksTriggeredAbility(new BoostSourceEffect( + SourcePermanentPowerValue.ALLOW_NEGATIVE, StaticValue.get(0), Duration.EndOfTurn + ).setText("double its power until end of turn"))); + + // Whenever Overclocked Electromancer deals combat damage to a creature, if that creature was dealt excess damage this turn, you get X {E}, where X is that excess damage. + this.addAbility(new DealsDamageToACreatureTriggeredAbility( + new GetEnergyCountersControllerEffect(OverclockedElectromancerValue.instance) + .setText("you get X {E}, where X is that excess damage"), + true, false, true, filter + ), new OverclockedElectromancerWatcher()); + } + + private OverclockedElectromancer(final OverclockedElectromancer card) { + super(card); + } + + @Override + public OverclockedElectromancer copy() { + return new OverclockedElectromancer(this); + } +} + +enum OverclockedElectromancerPredicate implements Predicate { + instance; + + @Override + public boolean apply(Permanent input, Game game) { + return OverclockedElectromancerWatcher.getAmount(input, game) > 0; + } +} + +enum OverclockedElectromancerValue implements DynamicValue { + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return OverclockedElectromancerWatcher.getAmount((Permanent) effect.getValue("damagedCreature"), game); + } + + @Override + public OverclockedElectromancerValue copy() { + return this; + } + + @Override + public String getMessage() { + return ""; + } + + @Override + public String toString() { + return "X"; + } +} + +class OverclockedElectromancerWatcher extends Watcher { + + private final Map map = new HashMap<>(); + + OverclockedElectromancerWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() != GameEvent.EventType.DAMAGED_PERMANENT) { + return; + } + int excess = ((DamagedPermanentEvent) event).getExcess(); + if (excess > 0) { + map.compute( + new MageObjectReference(event.getTargetId(), game), + (mor, i) -> i == null ? excess : Integer.sum(i, excess) + ); + } + } + + @Override + public void reset() { + super.reset(); + map.clear(); + } + + public static int getAmount(Permanent permanent, Game game) { + return permanent != null ? + game.getState() + .getWatcher(OverclockedElectromancerWatcher.class) + .map + .getOrDefault(new MageObjectReference(permanent, game), 0) + : 0; + } +} diff --git a/Mage.Sets/src/mage/cards/p/PainsReward.java b/Mage.Sets/src/mage/cards/p/PainsReward.java index 3b8f2a94e65..544e62c6e82 100644 --- a/Mage.Sets/src/mage/cards/p/PainsReward.java +++ b/Mage.Sets/src/mage/cards/p/PainsReward.java @@ -60,7 +60,7 @@ class PainsRewardEffect extends OneShotEffect { playerList.setCurrent(controller.getId()); Player winner = game.getPlayer(controller.getId()); - int highBid = chooseLifeAmountToBid(controller, -1, game); // -1 for start with 0 min big + int highBid = chooseLifeAmountToBid(controller, -1, source, game); // -1 for start with 0 min big game.informPlayers(winner.getLogName() + " has bet " + highBid + " lifes"); Player currentPlayer = playerList.getNextInRange(controller, game); @@ -72,7 +72,7 @@ class PainsRewardEffect extends OneShotEffect { Outcome aiOutcome = (highBid + 1 <= safeLifeToLost) ? Outcome.Benefit : Outcome.Detriment; if (currentPlayer.chooseUse(aiOutcome, text, source, game)) { - int newBid = chooseLifeAmountToBid(currentPlayer, highBid, game); + int newBid = chooseLifeAmountToBid(currentPlayer, highBid, source, game); if (newBid > highBid) { highBid = newBid; winner = currentPlayer; @@ -90,14 +90,14 @@ class PainsRewardEffect extends OneShotEffect { return false; } - private int chooseLifeAmountToBid(Player player, int currentBig, Game game) { + private int chooseLifeAmountToBid(Player player, int currentBig, Ability source, Game game) { int newBid; if (player.isComputer()) { // AI choose newBid = currentBig + 1; } else { // Human choose - newBid = player.getAmount(currentBig + 1, Integer.MAX_VALUE, "Choose amount of life to bid", game); + newBid = player.getAmount(currentBig + 1, Integer.MAX_VALUE, "Choose amount of life to bid", source, game); } return newBid; } diff --git a/Mage.Sets/src/mage/cards/p/PaladinsArms.java b/Mage.Sets/src/mage/cards/p/PaladinsArms.java new file mode 100644 index 00000000000..287e41b7f89 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PaladinsArms.java @@ -0,0 +1,55 @@ +package mage.cards.p; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.continuous.AddCardSubtypeAttachedEffect; +import mage.abilities.effects.common.continuous.BoostEquippedEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.keyword.EquipAbility; +import mage.abilities.keyword.JobSelectAbility; +import mage.abilities.keyword.WardAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AttachmentType; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class PaladinsArms extends CardImpl { + + public PaladinsArms(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{W}"); + + this.subtype.add(SubType.EQUIPMENT); + + // Job select + this.addAbility(new JobSelectAbility()); + + // Equipped creature gets +2/+1, has ward {1}, and is a Knight in addition to its other types. + Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(2, 1)); + ability.addEffect(new GainAbilityAttachedEffect( + new WardAbility(new GenericManaCost(1)), AttachmentType.EQUIPMENT + ).setText(", has ward {1}")); + ability.addEffect(new AddCardSubtypeAttachedEffect( + SubType.KNIGHT, AttachmentType.EQUIPMENT + ).setText(", and is a Knight in addition to its other types")); + this.addAbility(ability); + + // Lightbringer and Hero's Shield -- Equip {4} + this.addAbility(new EquipAbility(4).withFlavorWord("Lightbringer and Hero's Shield")); + } + + private PaladinsArms(final PaladinsArms card) { + super(card); + } + + @Override + public PaladinsArms copy() { + return new PaladinsArms(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/Perennation.java b/Mage.Sets/src/mage/cards/p/Perennation.java index ad266c96dce..d289bcd9144 100644 --- a/Mage.Sets/src/mage/cards/p/Perennation.java +++ b/Mage.Sets/src/mage/cards/p/Perennation.java @@ -5,7 +5,8 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.counters.CounterType; -import mage.filter.StaticFilters; +import mage.filter.FilterCard; +import mage.filter.common.FilterPermanentCard; import mage.target.common.TargetCardInYourGraveyard; import java.util.UUID; @@ -15,6 +16,8 @@ import java.util.UUID; */ public final class Perennation extends CardImpl { + private static final FilterCard filter = new FilterPermanentCard("permanent card from your graveyard"); + public Perennation(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{W}{B}{G}"); @@ -22,7 +25,7 @@ public final class Perennation extends CardImpl { this.getSpellAbility().addEffect(new ReturnFromGraveyardToBattlefieldWithCounterTargetEffect( CounterType.HEXPROOF.createInstance(), CounterType.INDESTRUCTIBLE.createInstance() )); - this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_PERMANENT)); + this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(filter)); } private Perennation(final Perennation card) { diff --git a/Mage.Sets/src/mage/cards/p/PhantomTrain.java b/Mage.Sets/src/mage/cards/p/PhantomTrain.java new file mode 100644 index 00000000000..2e7f7480b82 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PhantomTrain.java @@ -0,0 +1,58 @@ +package mage.cards.p; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.effects.common.continuous.AddCardSubTypeSourceEffect; +import mage.abilities.effects.common.continuous.AddCardTypeSourceEffect; +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.Duration; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class PhantomTrain extends CardImpl { + + public PhantomTrain(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}{B}"); + + this.subtype.add(SubType.VEHICLE); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Sacrifice another artifact or creature: Put a +1/+1 counter on this Vehicle. It becomes a Spirit artifact creature in addition to its other types until end of turn. + Ability ability = new SimpleActivatedAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), + new SacrificeTargetCost(StaticFilters.FILTER_CONTROLLED_ANOTHER_CREATURE_OR_ARTIFACT) + ); + ability.addEffect(new AddCardTypeSourceEffect( + Duration.EndOfTurn, CardType.ARTIFACT, CardType.CREATURE + ).setText("it becomes a")); + ability.addEffect(new AddCardSubTypeSourceEffect( + Duration.EndOfTurn, true, SubType.SPIRIT + ).setText("Spirit artifact creature in addition to its other types until end of turn")); + this.addAbility(ability); + } + + private PhantomTrain(final PhantomTrain card) { + super(card); + } + + @Override + public PhantomTrain copy() { + return new PhantomTrain(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PhoenixDown.java b/Mage.Sets/src/mage/cards/p/PhoenixDown.java new file mode 100644 index 00000000000..a7cc549bf90 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PhoenixDown.java @@ -0,0 +1,69 @@ +package mage.cards.p; + +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.ExileSourceCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreatureCard; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.ManaValuePredicate; +import mage.target.TargetPermanent; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class PhoenixDown extends CardImpl { + + private static final FilterCard filter + = new FilterCreatureCard("creature card with mana value 4 or less from your graveyard"); + private static final FilterPermanent filter2 = new FilterPermanent("Skeleton, Spirit, or Zombie"); + + static { + filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, 5)); + filter2.add(Predicates.or( + SubType.SKELETON.getPredicate(), + SubType.SPIRIT.getPredicate(), + SubType.ZOMBIE.getPredicate() + )); + } + + public PhoenixDown(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{W}"); + + // {1}{W}, {T}, Exile this artifact: Choose one-- + // * Return target creature card with mana value 4 or less from your graveyard to the battlefield tapped. + Ability ability = new SimpleActivatedAbility( + new ReturnFromGraveyardToBattlefieldTargetEffect(true), new ManaCostsImpl<>("{1}{W}") + ); + ability.addCost(new TapSourceCost()); + ability.addCost(new ExileSourceCost()); + ability.addTarget(new TargetCardInYourGraveyard(filter)); + + // * Exile target Skeleton, Spirit, or Zombie. + ability.addMode(new Mode(new ExileTargetEffect()).addTarget(new TargetPermanent(filter2))); + this.addAbility(ability); + } + + private PhoenixDown(final PhoenixDown card) { + super(card); + } + + @Override + public PhoenixDown copy() { + return new PhoenixDown(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PhyrexianProcessor.java b/Mage.Sets/src/mage/cards/p/PhyrexianProcessor.java index 011c55daf31..757ff74965b 100644 --- a/Mage.Sets/src/mage/cards/p/PhyrexianProcessor.java +++ b/Mage.Sets/src/mage/cards/p/PhyrexianProcessor.java @@ -74,7 +74,7 @@ class PhyrexianProcessorPayLifeEffect extends OneShotEffect { if (controller == null || permanent == null) { return false; } - int payAmount = controller.getAmount(0, controller.getLife(), "Pay any amount of life", game); + int payAmount = controller.getAmount(0, controller.getLife(), "Pay any amount of life", source, game); Cost cost = new PayLifeCost(payAmount); if (!cost.pay(source, game, source, source.getControllerId(), true)) { return false; diff --git a/Mage.Sets/src/mage/cards/p/PiaNalaarChiefMechanic.java b/Mage.Sets/src/mage/cards/p/PiaNalaarChiefMechanic.java index 02b816c6268..59f4a8d8b49 100644 --- a/Mage.Sets/src/mage/cards/p/PiaNalaarChiefMechanic.java +++ b/Mage.Sets/src/mage/cards/p/PiaNalaarChiefMechanic.java @@ -83,7 +83,7 @@ class PiaNalaarChiefMechanicEffect extends OneShotEffect { return false; } int energyToPay = controller.getAmount(1, controller.getCountersCount(CounterType.ENERGY), - "Pay 1 or more {E}", game); + "Pay 1 or more {E}", source, game); if (energyToPay == 0) { return true; } diff --git a/Mage.Sets/src/mage/cards/p/PitOfOfferings.java b/Mage.Sets/src/mage/cards/p/PitOfOfferings.java index 64aad8b2e8d..1d805f3c44a 100644 --- a/Mage.Sets/src/mage/cards/p/PitOfOfferings.java +++ b/Mage.Sets/src/mage/cards/p/PitOfOfferings.java @@ -28,6 +28,7 @@ import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.TargetCard; +import mage.target.common.TargetCardInGraveyard; import mage.util.CardUtil; import java.util.*; @@ -47,7 +48,9 @@ public final class PitOfOfferings extends CardImpl { this.addAbility(new EntersBattlefieldTappedAbility()); // When Pit of Offerings enters the battlefield, exile up to three target cards from graveyards. - this.addAbility(new EntersBattlefieldTriggeredAbility(new PitOfOfferingsEffect(), false)); + Ability ability = new EntersBattlefieldTriggeredAbility(new PitOfOfferingsEffect(), false); + ability.addTarget(new TargetCardInGraveyard(0, 3)); + this.addAbility(ability); // {T}: Add {C}. this.addAbility(new ColorlessManaAbility()); @@ -124,8 +127,6 @@ class PitOfOfferingsEffect extends OneShotEffect { ), game); } - private static final FilterCard filter = new FilterCard("cards from graveyards"); - public PitOfOfferingsEffect() { super(Outcome.Benefit); staticText = "exile up to three target cards from graveyards"; @@ -142,16 +143,11 @@ class PitOfOfferingsEffect extends OneShotEffect { if (controller == null || sourceObject == null) { return false; } - - TargetCard target = new TargetCard(0, 3, Zone.GRAVEYARD, filter); - if (target.choose(outcome, source.getControllerId(), source.getSourceId(), source, game)) { - Cards cardsExiled = new CardsImpl(); - cardsExiled.addAll(target.getTargets()); - controller.moveCardsToExile( - cardsExiled.getCards(game), source, game, true, - getExileZoneId(sourceObject, game), sourceObject.getIdName() - ); - } + Cards cardsExiled = new CardsImpl(getTargetPointer().getTargets(game, source)); + controller.moveCardsToExile( + cardsExiled.getCards(game), source, game, true, + getExileZoneId(sourceObject, game), sourceObject.getIdName() + ); return true; } diff --git a/Mage.Sets/src/mage/cards/p/PlagueOfVermin.java b/Mage.Sets/src/mage/cards/p/PlagueOfVermin.java index 267235cc9fd..bf5b72355bf 100644 --- a/Mage.Sets/src/mage/cards/p/PlagueOfVermin.java +++ b/Mage.Sets/src/mage/cards/p/PlagueOfVermin.java @@ -78,7 +78,7 @@ class PlagueOfVerminEffect extends OneShotEffect { currentLifePaid = 0; totalPaidLife = 0; if (currentPlayer.chooseUse(Outcome.AIDontUseIt, "Pay life?", source, game)) { - totalPaidLife = currentPlayer.getAmount(0, controller.getLife(), "Pay how many life?", game); + totalPaidLife = currentPlayer.getAmount(0, controller.getLife(), "Pay how many life?", source, game); if (totalPaidLife > 0) { currentPlayer.loseLife(totalPaidLife, game, source, false); if (payLife.get(currentPlayer.getId()) == null) { diff --git a/Mage.Sets/src/mage/cards/p/PleaForGuidance.java b/Mage.Sets/src/mage/cards/p/PleaForGuidance.java index 2274e7bfe2d..b154c2edb84 100644 --- a/Mage.Sets/src/mage/cards/p/PleaForGuidance.java +++ b/Mage.Sets/src/mage/cards/p/PleaForGuidance.java @@ -1,31 +1,24 @@ - package mage.cards.p; -import java.util.UUID; import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.FilterCard; +import mage.filter.StaticFilters; import mage.target.common.TargetCardInLibrary; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class PleaForGuidance extends CardImpl { - private static final FilterCard filter = new FilterCard("enchantment cards"); - static { - filter.add(CardType.ENCHANTMENT.getPredicate()); - } - public PleaForGuidance(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{5}{W}"); - + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{5}{W}"); // Search your library for up to two enchantment cards, reveal them, and put them into your hand. Then shuffle your library. - this.getSpellAbility().addEffect(new SearchLibraryPutInHandEffect(new TargetCardInLibrary(0,2, filter), true)); + this.getSpellAbility().addEffect(new SearchLibraryPutInHandEffect(new TargetCardInLibrary(0, 2, StaticFilters.FILTER_CARD_ENCHANTMENTS), true)); } private PleaForGuidance(final PleaForGuidance card) { diff --git a/Mage.Sets/src/mage/cards/p/PoisonTheWaters.java b/Mage.Sets/src/mage/cards/p/PoisonTheWaters.java new file mode 100644 index 00000000000..ef6a1cda1d8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PoisonTheWaters.java @@ -0,0 +1,39 @@ +package mage.cards.p; + +import mage.abilities.Mode; +import mage.abilities.effects.common.continuous.BoostAllEffect; +import mage.abilities.effects.common.discard.DiscardCardYouChooseTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.filter.StaticFilters; +import mage.target.TargetPlayer; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class PoisonTheWaters extends CardImpl { + + public PoisonTheWaters(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{B}"); + + // Choose one -- + // * All creatures get -1/-1 until end of turn. + this.getSpellAbility().addEffect(new BoostAllEffect(-1, -1, Duration.EndOfTurn)); + + // * Target player reveals their hand. You choose an artifact or creature card from it. That player discards that card. + this.getSpellAbility().addMode(new Mode(new DiscardCardYouChooseTargetEffect(StaticFilters.FILTER_CARD_ARTIFACT_OR_CREATURE)).addTarget(new TargetPlayer())); + } + + private PoisonTheWaters(final PoisonTheWaters card) { + super(card); + } + + @Override + public PoisonTheWaters copy() { + return new PoisonTheWaters(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/Polliwallop.java b/Mage.Sets/src/mage/cards/p/Polliwallop.java index 86f6847621f..5ea92391255 100644 --- a/Mage.Sets/src/mage/cards/p/Polliwallop.java +++ b/Mage.Sets/src/mage/cards/p/Polliwallop.java @@ -1,10 +1,9 @@ package mage.cards.p; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.AffinityEffect; import mage.abilities.effects.common.DamageWithPowerFromOneToAnotherTargetEffect; -import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; import mage.abilities.hint.Hint; import mage.abilities.hint.ValueHint; import mage.cards.CardImpl; @@ -24,19 +23,14 @@ import java.util.UUID; */ public final class Polliwallop extends CardImpl { - private static final DynamicValue xValue = new PermanentsOnBattlefieldCount( - new FilterControlledPermanent(SubType.FROG, "Frog you control") - ); - private static final Hint hint = new ValueHint("Frogs you control", xValue); + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.FROG, "Frogs"); + private static final Hint hint = new ValueHint("Frogs you control", new PermanentsOnBattlefieldCount(filter)); public Polliwallop(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{G}"); // This spell costs {1} less to cast for each Frog you control. - this.addAbility(new SimpleStaticAbility( - Zone.ALL, - new SpellCostReductionForEachSourceEffect(1, xValue).setCanWorksOnStackOnly(true) - ).setRuleAtTheTop(true).addHint(hint)); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new AffinityEffect(filter)).setRuleAtTheTop(true).addHint(hint)); // Target creature you control deals damage equal to twice its power to target creature you don't control. this.getSpellAbility().addEffect(new DamageWithPowerFromOneToAnotherTargetEffect("", 2)); diff --git a/Mage.Sets/src/mage/cards/p/PowerLeak.java b/Mage.Sets/src/mage/cards/p/PowerLeak.java index 8acc101045c..82e7049363d 100644 --- a/Mage.Sets/src/mage/cards/p/PowerLeak.java +++ b/Mage.Sets/src/mage/cards/p/PowerLeak.java @@ -80,7 +80,7 @@ class PowerLeakEffect extends OneShotEffect { String message = "Pay {X} to prevent X damage from " + permanent.getLogName() + "?"; int xValue = 0; if (player.chooseUse(Outcome.Neutral, message, source, game)) { - xValue = player.announceXMana(0, Integer.MAX_VALUE, "Choose the amount of mana to pay", game, source); + xValue = player.announceX(0, Integer.MAX_VALUE, "Announce the value for {X} (pay to prevent damage)", game, source, true); cost.add(new GenericManaCost(xValue)); if (cost.pay(source, game, source, player.getId(), false, null)) { game.informPlayers(player.getLogName() + " paid {" + xValue + "} for " + permanent.getLogName()); diff --git a/Mage.Sets/src/mage/cards/p/PrestonGarveyMinuteman.java b/Mage.Sets/src/mage/cards/p/PrestonGarveyMinuteman.java new file mode 100644 index 00000000000..56959fc7313 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PrestonGarveyMinuteman.java @@ -0,0 +1,91 @@ +package mage.cards.p; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.UntapAllEffect; +import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; +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.StaticFilters; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.permanent.EnchantedPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.SettlementToken; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class PrestonGarveyMinuteman extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledPermanent("each enchanted permanent you control"); + + static { + filter.add(EnchantedPredicate.instance); + } + + public PrestonGarveyMinuteman(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}{G}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // At the beginning of combat on your turn, create a green Aura enchantment token named Settlement attached to up to one target land you control. It has enchant land and "Enchanted land has '{T}: Add one mana of any color.'" + Ability ability = new BeginningOfCombatTriggeredAbility(new PrestonGarveyMinutemanEffect()); + ability.addTarget(new TargetPermanent(0, 1, StaticFilters.FILTER_CONTROLLED_PERMANENT_LAND)); + this.addAbility(ability); + + // Whenever Preston Garvey, Minuteman attacks, untap each enchanted permanent you control. + this.addAbility(new AttacksTriggeredAbility(new UntapAllEffect(filter))); + } + + private PrestonGarveyMinuteman(final PrestonGarveyMinuteman card) { + super(card); + } + + @Override + public PrestonGarveyMinuteman copy() { + return new PrestonGarveyMinuteman(this); + } +} + +class PrestonGarveyMinutemanEffect extends OneShotEffect { + + PrestonGarveyMinutemanEffect() { + super(Outcome.Benefit); + staticText = "create a green Aura enchantment token named Settlement " + + "attached to up to one target land you control. It has enchant land and " + + "\"Enchanted land has '{T}: Add one mana of any color.'\""; + } + + private PrestonGarveyMinutemanEffect(final PrestonGarveyMinutemanEffect effect) { + super(effect); + } + + @Override + public PrestonGarveyMinutemanEffect copy() { + return new PrestonGarveyMinutemanEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + return permanent != null && new SettlementToken().putOntoBattlefield( + 1, game, source, source.getControllerId(), false, + false, null, permanent.getId() + ); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PriceOfBetrayal.java b/Mage.Sets/src/mage/cards/p/PriceOfBetrayal.java index bf0d5d6285f..8de18f96784 100644 --- a/Mage.Sets/src/mage/cards/p/PriceOfBetrayal.java +++ b/Mage.Sets/src/mage/cards/p/PriceOfBetrayal.java @@ -88,7 +88,7 @@ class PriceOfBetrayalEffect extends OneShotEffect { permanent.removeCounters(counterName, 1, source, game); removed++; } else { - int amount = controller.getAmount(1, Math.min(permanent.getCounters(game).get(counterName).getCount(), toRemove - removed), "How many?", game); + int amount = controller.getAmount(1, Math.min(permanent.getCounters(game).get(counterName).getCount(), toRemove - removed), "How many?", source, game); if (amount > 0) { removed += amount; permanent.removeCounters(counterName, amount, source, game); @@ -114,7 +114,7 @@ class PriceOfBetrayalEffect extends OneShotEffect { player.loseCounters(counterName, 1, source, game); removed++; } else { - int amount = controller.getAmount(1, Math.min(player.getCountersCount(counterName), toRemove - removed), "How many?", game); + int amount = controller.getAmount(1, Math.min(player.getCountersCount(counterName), toRemove - removed), "How many?", source, game); if (amount > 0) { removed += amount; player.loseCounters(counterName, amount, source, game); diff --git a/Mage.Sets/src/mage/cards/p/ProtectionMagic.java b/Mage.Sets/src/mage/cards/p/ProtectionMagic.java new file mode 100644 index 00000000000..7f7df7a4896 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/ProtectionMagic.java @@ -0,0 +1,33 @@ +package mage.cards.p; + +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.counters.CounterType; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ProtectionMagic extends CardImpl { + + public ProtectionMagic(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{W}"); + + // Put a shield counter on each of up to three target creatures. + this.getSpellAbility().addEffect(new AddCountersTargetEffect(CounterType.SHIELD.createInstance())); + this.getSpellAbility().addTarget(new TargetCreaturePermanent(0, 3)); + } + + private ProtectionMagic(final ProtectionMagic card) { + super(card); + } + + @Override + public ProtectionMagic copy() { + return new ProtectionMagic(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PuPuUFO.java b/Mage.Sets/src/mage/cards/p/PuPuUFO.java new file mode 100644 index 00000000000..9e1fac28305 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PuPuUFO.java @@ -0,0 +1,67 @@ +package mage.cards.p; + +import mage.MageInt; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.effects.common.PutCardFromHandOntoBattlefieldEffect; +import mage.abilities.effects.common.continuous.SetBasePowerToughnessSourceEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +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 mage.filter.common.FilterControlledPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class PuPuUFO extends CardImpl { + + private static final DynamicValue xValue + = new PermanentsOnBattlefieldCount(new FilterControlledPermanent(SubType.TOWN)); + private static final Hint hint = new ValueHint("Towns you control", xValue); + + public PuPuUFO(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{2}"); + + this.subtype.add(SubType.CONSTRUCT); + this.subtype.add(SubType.ALIEN); + this.power = new MageInt(0); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // {T}: You may put a land card from your hand onto the battlefield. + this.addAbility(new SimpleActivatedAbility( + new PutCardFromHandOntoBattlefieldEffect(StaticFilters.FILTER_CARD_LAND_A), new TapSourceCost() + )); + + // {3}: Until end of turn, this creature's base power becomes equal to the number of Towns you control. + this.addAbility(new SimpleActivatedAbility( + new SetBasePowerToughnessSourceEffect( + xValue, StaticValue.get(0), Duration.EndOfTurn, "until end of turn, " + + "this creature's base power becomes equal to the number of Towns you control" + ), new GenericManaCost(3) + ).addHint(hint)); + } + + private PuPuUFO(final PuPuUFO card) { + super(card); + } + + @Override + public PuPuUFO copy() { + return new PuPuUFO(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PushPull.java b/Mage.Sets/src/mage/cards/p/PushPull.java index 7c829f6960f..e0a55f6c229 100644 --- a/Mage.Sets/src/mage/cards/p/PushPull.java +++ b/Mage.Sets/src/mage/cards/p/PushPull.java @@ -1,10 +1,5 @@ package mage.cards.p; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; - import mage.abilities.Ability; import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; import mage.abilities.effects.ContinuousEffect; @@ -29,7 +24,12 @@ import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.common.TargetCardInASingleGraveyard; import mage.target.common.TargetCreaturePermanent; -import mage.target.targetpointer.FixedTarget; +import mage.target.targetpointer.FixedTargets; + +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; /** * @@ -108,18 +108,13 @@ class PullEffect extends OneShotEffect { return false; } - permanents.forEach(permanent -> { - FixedTarget blueprintTarget = new FixedTarget(permanent, game); - - ContinuousEffect effect = new GainAbilityTargetEffect(HasteAbility.getInstance()); - effect.setTargetPointer(blueprintTarget.copy()); - game.addEffect(effect, source); - - Effect sacrificeEffect = new SacrificeTargetEffect("sacrifice " + permanent.getLogName(), controller.getId()); - sacrificeEffect.setTargetPointer(blueprintTarget.copy()); - game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(sacrificeEffect), source); - }); + ContinuousEffect effect = new GainAbilityTargetEffect(HasteAbility.getInstance()); + effect.setTargetPointer(new FixedTargets(permanents, game)); + game.addEffect(effect, source); + Effect sacrificeEffect = new SacrificeTargetEffect("sacrifice them", controller.getId()); + sacrificeEffect.setTargetPointer(new FixedTargets(permanents, game)); + game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(sacrificeEffect), source); return true; } } diff --git a/Mage.Sets/src/mage/cards/q/QuilledGreatwurm.java b/Mage.Sets/src/mage/cards/q/QuilledGreatwurm.java index ead1c64e929..4d9ded412ed 100644 --- a/Mage.Sets/src/mage/cards/q/QuilledGreatwurm.java +++ b/Mage.Sets/src/mage/cards/q/QuilledGreatwurm.java @@ -74,7 +74,7 @@ class QuilledGreatwurmEffect extends AsThoughEffectImpl { QuilledGreatwurmEffect() { super(AsThoughEffectType.CAST_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfGame, Outcome.Benefit); - this.staticText = "you may cast {this} from your graveyard by removing six counters " + + this.staticText = "you may cast this card from your graveyard by removing six counters " + "from among creatures you control in addition to paying its other costs"; } diff --git a/Mage.Sets/src/mage/cards/q/QuistisTrepe.java b/Mage.Sets/src/mage/cards/q/QuistisTrepe.java new file mode 100644 index 00000000000..c3614f37b37 --- /dev/null +++ b/Mage.Sets/src/mage/cards/q/QuistisTrepe.java @@ -0,0 +1,51 @@ +package mage.cards.q; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.MayCastTargetCardEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.CastManaAdjustment; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterCard; +import mage.filter.common.FilterInstantOrSorceryCard; +import mage.target.common.TargetCardInGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class QuistisTrepe extends CardImpl { + + private static final FilterCard filter = new FilterInstantOrSorceryCard("instant or sorcery card from a graveyard"); + + public QuistisTrepe(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Blue Magic -- When Quistis Trepe enters, you may cast target instant or sorcery card from a graveyard, and mana of any type can be spent to cast that spell. If that spell would be put into a graveyard, exile it instead. + Ability ability = new EntersBattlefieldTriggeredAbility( + new MayCastTargetCardEffect(CastManaAdjustment.AS_THOUGH_ANY_MANA_TYPE, true) + ); + this.getSpellAbility().addTarget(new TargetCardInGraveyard(filter)); + this.addAbility(ability.withFlavorWord("Blue Magic")); + } + + private QuistisTrepe(final QuistisTrepe card) { + super(card); + } + + @Override + public QuistisTrepe copy() { + return new QuistisTrepe(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RabanastreRoyalCity.java b/Mage.Sets/src/mage/cards/r/RabanastreRoyalCity.java new file mode 100644 index 00000000000..6ea04309895 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RabanastreRoyalCity.java @@ -0,0 +1,39 @@ +package mage.cards.r; + +import mage.abilities.common.EntersBattlefieldTappedAbility; +import mage.abilities.mana.RedManaAbility; +import mage.abilities.mana.WhiteManaAbility; +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 RabanastreRoyalCity extends CardImpl { + + public RabanastreRoyalCity(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + this.subtype.add(SubType.TOWN); + + // This land enters tapped. + this.addAbility(new EntersBattlefieldTappedAbility()); + + // {T}: Add {R} or {W}. + this.addAbility(new RedManaAbility()); + this.addAbility(new WhiteManaAbility()); + } + + private RabanastreRoyalCity(final RabanastreRoyalCity card) { + super(card); + } + + @Override + public RabanastreRoyalCity copy() { + return new RabanastreRoyalCity(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RaggadraggaGoregutsBoss.java b/Mage.Sets/src/mage/cards/r/RaggadraggaGoregutsBoss.java index fc6afae7fc0..8f6d8d56b4c 100644 --- a/Mage.Sets/src/mage/cards/r/RaggadraggaGoregutsBoss.java +++ b/Mage.Sets/src/mage/cards/r/RaggadraggaGoregutsBoss.java @@ -17,11 +17,10 @@ import mage.constants.*; import mage.filter.FilterSpell; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.Predicate; +import mage.filter.predicate.mageobject.ManaSpentToCastPredicate; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.game.stack.StackObject; import mage.target.common.TargetCreaturePermanent; -import mage.watchers.common.ManaPaidSourceWatcher; import java.util.UUID; @@ -32,12 +31,12 @@ public final class RaggadraggaGoregutsBoss extends CardImpl { private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature you control with a mana ability"); - private static final FilterSpell filter2 = new FilterSpell(); + private static final FilterSpell filter2 = new FilterSpell("a spell, if at least seven mana was spent to cast it"); static { filter.add(TargetController.YOU.getOwnerPredicate()); filter.add(RaggadraggaGoregutsBossCreaturePredicate.instance); - filter2.add(RaggadraggaGoregutsBossSpellPredicate.instance); + filter2.add(new ManaSpentToCastPredicate(ComparisonType.MORE_THAN, 6)); } public RaggadraggaGoregutsBoss(UUID ownerId, CardSetInfo setInfo) { @@ -61,9 +60,7 @@ public final class RaggadraggaGoregutsBoss extends CardImpl { )); // Whenever you cast a spell, if at least seven mana was spent to cast it, untap target creature. It gets +7/+7 and gains trample until end of turn. - Ability ability = new SpellCastControllerTriggeredAbility( - new UntapTargetEffect(), filter2, false - ).setTriggerPhrase("Whenever you cast a spell, if at least seven mana was spent to cast it, "); + Ability ability = new SpellCastControllerTriggeredAbility(new UntapTargetEffect(), filter2, false); ability.addEffect(new BoostTargetEffect(7, 7).setText("It gets +7/+7")); ability.addEffect(new GainAbilityTargetEffect( TrampleAbility.getInstance(), Duration.EndOfTurn @@ -90,12 +87,3 @@ enum RaggadraggaGoregutsBossCreaturePredicate implements Predicate { return input.getAbilities(game).stream().anyMatch(ManaAbility.class::isInstance); } } - -enum RaggadraggaGoregutsBossSpellPredicate implements Predicate { - instance; - - @Override - public boolean apply(StackObject input, Game game) { - return ManaPaidSourceWatcher.getTotalPaid(input.getId(), game) >= 7; - } -} diff --git a/Mage.Sets/src/mage/cards/r/RagnarokDivineDeliverance.java b/Mage.Sets/src/mage/cards/r/RagnarokDivineDeliverance.java new file mode 100644 index 00000000000..4ac7c9dddf8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RagnarokDivineDeliverance.java @@ -0,0 +1,78 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DiesSourceTriggeredAbility; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.abilities.keyword.*; +import mage.cards.CardSetInfo; +import mage.cards.MeldCard; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterCard; +import mage.filter.common.FilterPermanentCard; +import mage.filter.predicate.Predicates; +import mage.target.TargetPermanent; +import mage.target.common.TargetCardInYourGraveyard; +import mage.target.targetpointer.SecondTargetPointer; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RagnarokDivineDeliverance extends MeldCard { + + private static final FilterCard filter = new FilterPermanentCard("nonlegendary permanent card from your graveyard"); + + static { + filter.add(Predicates.not(SuperType.LEGENDARY.getPredicate())); + } + + public RagnarokDivineDeliverance(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.BEAST); + this.subtype.add(SubType.AVATAR); + this.power = new MageInt(7); + this.toughness = new MageInt(6); + this.color.setBlack(true); + this.color.setGreen(true); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Menace + this.addAbility(new MenaceAbility()); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // When Ragnarok dies, destroy target permanent and return target nonlegendary permanent card from your graveyard to the battlefield. + Ability ability = new DiesSourceTriggeredAbility(new DestroyTargetEffect()); + ability.addEffect(new ReturnFromGraveyardToBattlefieldTargetEffect() + .setText("and return target nonlegendary permanent card from your graveyard to the battlefield") + .setTargetPointer(new SecondTargetPointer())); + ability.addTarget(new TargetPermanent()); + ability.addTarget(new TargetCardInYourGraveyard(filter)); + this.addAbility(ability); + } + + private RagnarokDivineDeliverance(final RagnarokDivineDeliverance card) { + super(card); + } + + @Override + public RagnarokDivineDeliverance copy() { + return new RagnarokDivineDeliverance(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RampagingAetherhood.java b/Mage.Sets/src/mage/cards/r/RampagingAetherhood.java index d8c9f5710f7..8f08c10d92c 100644 --- a/Mage.Sets/src/mage/cards/r/RampagingAetherhood.java +++ b/Mage.Sets/src/mage/cards/r/RampagingAetherhood.java @@ -86,7 +86,7 @@ class RampagingAetherhoodEffect extends OneShotEffect { int totalEnergy = controller.getCountersCount(CounterType.ENERGY); if (totalEnergy > 0) { if (controller.chooseUse(Outcome.Benefit, "Pay one or more {E}? Put that many +1/+1 counters on this creature", source, game)) { - int energyToPay = controller.getAmount(1, totalEnergy, "Pay one or more {E}", game); + int energyToPay = controller.getAmount(1, totalEnergy, "Pay one or more {E}", source, game); Cost cost = new PayEnergyCost(energyToPay); if (cost.pay(source, game, source, controller.getId(), true)) { new AddCountersSourceEffect(CounterType.P1P1.createInstance(energyToPay), true).apply(game, source); diff --git a/Mage.Sets/src/mage/cards/r/RampantFrogantua.java b/Mage.Sets/src/mage/cards/r/RampantFrogantua.java new file mode 100644 index 00000000000..acbea57bae6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RampantFrogantua.java @@ -0,0 +1,121 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.SavedDamageValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.watchers.common.PlayerLostGameWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RampantFrogantua extends CardImpl { + + public RampantFrogantua(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.subtype.add(SubType.FROG); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Rampant Frogantua gets +10/+10 for each player who has lost the game. + this.addAbility(new SimpleStaticAbility(new BoostSourceEffect( + RampantFrogantuaValue.instance, RampantFrogantuaValue.instance, Duration.WhileOnBattlefield + )), new PlayerLostGameWatcher()); + + // Whenever Rampant Frogantua deals combat damage to a player, you may mill that many cards. Put any number of land cards from among them onto the battlefield tapped. + this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility(new RampantFrogantuaEffect(), true)); + } + + private RampantFrogantua(final RampantFrogantua card) { + super(card); + } + + @Override + public RampantFrogantua copy() { + return new RampantFrogantua(this); + } +} + +class RampantFrogantuaEffect extends OneShotEffect { + + RampantFrogantuaEffect() { + super(Outcome.Benefit); + staticText = "mill that many cards. Put any number of land cards from among them onto the battlefield tapped"; + } + + private RampantFrogantuaEffect(final RampantFrogantuaEffect effect) { + super(effect); + } + + @Override + public RampantFrogantuaEffect copy() { + return new RampantFrogantuaEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + int amount = SavedDamageValue.MANY.calculate(game, source, this); + if (player == null || amount < 1) { + return false; + } + Cards cards = player.millCards(amount, source, game); + if (cards.isEmpty()) { + return true; + } + TargetCard target = new TargetCard(0, Integer.MAX_VALUE, Zone.ALL, StaticFilters.FILTER_CARD_LANDS); + target.withNotTarget(true); + player.choose(Outcome.DrawCard, cards, target, source, game); + player.moveCards( + new CardsImpl(target.getTargets()).getCards(game), + Zone.BATTLEFIELD, source, game, true, + false, false, null + ); + return true; + } +} + +enum RampantFrogantuaValue implements DynamicValue { + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return 10 * PlayerLostGameWatcher.getCount(game); + } + + @Override + public RampantFrogantuaValue copy() { + return this; + } + + @Override + public String getMessage() { + return "player who has lost the game"; + } + + @Override + public String toString() { + return "10"; + } +} diff --git a/Mage.Sets/src/mage/cards/r/RampartArchitect.java b/Mage.Sets/src/mage/cards/r/RampartArchitect.java index fd1a0bfde46..eb022ce2ccb 100644 --- a/Mage.Sets/src/mage/cards/r/RampartArchitect.java +++ b/Mage.Sets/src/mage/cards/r/RampartArchitect.java @@ -44,7 +44,7 @@ public final class RampartArchitect extends CardImpl { // Whenever a creature you control with defender dies, you may search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. this.addAbility(new DiesCreatureTriggeredAbility(new SearchLibraryPutInPlayEffect( - new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_LAND), true + new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_LAND), true, true ), true, filter)); } diff --git a/Mage.Sets/src/mage/cards/r/RealityHeist.java b/Mage.Sets/src/mage/cards/r/RealityHeist.java index 2c149e98b8c..fc4653a1c1a 100644 --- a/Mage.Sets/src/mage/cards/r/RealityHeist.java +++ b/Mage.Sets/src/mage/cards/r/RealityHeist.java @@ -1,15 +1,11 @@ package mage.cards.r; -import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.dynamicvalue.common.ArtifactYouControlCount; import mage.abilities.effects.common.LookLibraryAndPickControllerEffect; -import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; -import mage.abilities.hint.common.ArtifactYouControlHint; +import mage.abilities.keyword.AffinityForArtifactsAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.PutCards; -import mage.constants.Zone; import mage.filter.StaticFilters; import java.util.UUID; @@ -23,9 +19,7 @@ public final class RealityHeist extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{5}{U}{U}"); // This spell costs {1} less to cast for each artifact you control. - this.addAbility(new SimpleStaticAbility(Zone.ALL, - new SpellCostReductionForEachSourceEffect(1, ArtifactYouControlCount.instance) - ).addHint(ArtifactYouControlHint.instance)); + this.addAbility(new AffinityForArtifactsAbility()); // Look at the top seven cards of your library. You may reveal up to two artifact cards from among them and put them into your hand. Put the rest on the bottom of your library in a random order. this.getSpellAbility().addEffect(new LookLibraryAndPickControllerEffect( diff --git a/Mage.Sets/src/mage/cards/r/RedXIIIProudWarrior.java b/Mage.Sets/src/mage/cards/r/RedXIIIProudWarrior.java new file mode 100644 index 00000000000..ebe56a52fc7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RedXIIIProudWarrior.java @@ -0,0 +1,80 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.TrampleAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterCard; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.permanent.ModifiedPredicate; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RedXIIIProudWarrior extends CardImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent("modified creatures"); + private static final FilterCard filter2 = new FilterCard("Aura or Equipment card from your graveyard"); + + static { + filter.add(ModifiedPredicate.instance); + filter.add(Predicates.or( + SubType.AURA.getPredicate(), + SubType.EQUIPMENT.getPredicate() + )); + } + + public RedXIIIProudWarrior(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.BEAST); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Other modified creatures you control have vigilance and trample. + Ability ability = new SimpleStaticAbility(new GainAbilityControlledEffect( + VigilanceAbility.getInstance(), Duration.WhileOnBattlefield, filter, true + )); + ability.addEffect(new GainAbilityControlledEffect( + TrampleAbility.getInstance(), Duration.WhileOnBattlefield, filter, true + ).setText("and trample")); + this.addAbility(ability); + + // Cosmo Memory -- When Red XIII enters, return target Aura or Equipment card from your graveyard to your hand. + ability = new EntersBattlefieldTriggeredAbility(new ReturnFromGraveyardToHandTargetEffect()); + ability.addTarget(new TargetCardInYourGraveyard(filter2)); + this.addAbility(ability.withFlavorWord("Cosmo Memory")); + } + + private RedXIIIProudWarrior(final RedXIIIProudWarrior card) { + super(card); + } + + @Override + public RedXIIIProudWarrior copy() { + return new RedXIIIProudWarrior(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/ReduceInStature.java b/Mage.Sets/src/mage/cards/r/ReduceInStature.java index 061512ec3f8..31634e8b169 100644 --- a/Mage.Sets/src/mage/cards/r/ReduceInStature.java +++ b/Mage.Sets/src/mage/cards/r/ReduceInStature.java @@ -1,23 +1,21 @@ - package mage.cards.r; -import java.util.UUID; -import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.AttachEffect; -import mage.abilities.effects.common.continuous.SetBasePowerToughnessEnchantedEffect; +import mage.abilities.effects.common.continuous.SetBasePowerToughnessAttachedEffect; import mage.abilities.keyword.EnchantAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.constants.AttachmentType; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.Outcome; -import mage.constants.Zone; +import mage.constants.SubType; import mage.target.TargetPermanent; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class ReduceInStature extends CardImpl { @@ -30,12 +28,10 @@ public final class ReduceInStature extends CardImpl { TargetPermanent auraTarget = new TargetCreaturePermanent(); this.getSpellAbility().addTarget(auraTarget); this.getSpellAbility().addEffect(new AttachEffect(Outcome.UnboostCreature)); - Ability ability = new EnchantAbility(auraTarget); - this.addAbility(ability); + this.addAbility(new EnchantAbility(auraTarget)); // Enchanted creature has base power and toughness 0/2. - this.addAbility(new SimpleStaticAbility(new SetBasePowerToughnessEnchantedEffect())); - + this.addAbility(new SimpleStaticAbility(new SetBasePowerToughnessAttachedEffect(0, 2, AttachmentType.AURA))); } private ReduceInStature(final ReduceInStature card) { diff --git a/Mage.Sets/src/mage/cards/r/RelmsSketching.java b/Mage.Sets/src/mage/cards/r/RelmsSketching.java new file mode 100644 index 00000000000..a41d9432e75 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RelmsSketching.java @@ -0,0 +1,44 @@ +package mage.cards.r; + +import mage.abilities.effects.common.CreateTokenCopyTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.FilterPermanent; +import mage.filter.predicate.Predicates; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RelmsSketching extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("artifact, creature, or land"); + + static { + filter.add(Predicates.or( + CardType.ARTIFACT.getPredicate(), + CardType.CREATURE.getPredicate(), + CardType.LAND.getPredicate() + )); + } + + public RelmsSketching(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{U}{U}"); + + // Create a token that's a copy of target artifact, creature, or land. + this.getSpellAbility().addEffect(new CreateTokenCopyTargetEffect()); + this.getSpellAbility().addTarget(new TargetPermanent(filter)); + } + + private RelmsSketching(final RelmsSketching card) { + super(card); + } + + @Override + public RelmsSketching copy() { + return new RelmsSketching(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RemnantOfTheRisingStar.java b/Mage.Sets/src/mage/cards/r/RemnantOfTheRisingStar.java index a1a22a47838..dc9338acb2e 100644 --- a/Mage.Sets/src/mage/cards/r/RemnantOfTheRisingStar.java +++ b/Mage.Sets/src/mage/cards/r/RemnantOfTheRisingStar.java @@ -116,7 +116,7 @@ class RemnantOfTheRisingStarEffect extends OneShotEffect { )) { return false; } - int xValue = player.announceXMana(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source); + int xValue = player.announceX(0, Integer.MAX_VALUE, "Announce the value for {X} (pay to add counters)", game, source, true); cost.add(new GenericManaCost(xValue)); if (!cost.pay(source, game, source, source.getControllerId(), false, null)) { return false; diff --git a/Mage.Sets/src/mage/cards/r/RenderInert.java b/Mage.Sets/src/mage/cards/r/RenderInert.java index 7a366c9129c..eca89427255 100644 --- a/Mage.Sets/src/mage/cards/r/RenderInert.java +++ b/Mage.Sets/src/mage/cards/r/RenderInert.java @@ -72,7 +72,7 @@ class RenderInertEffect extends OneShotEffect { permanent.removeCounters(counterName, 1, source, game); removed++; } else { - int amount = controller.getAmount(1, Math.min(permanent.getCounters(game).get(counterName).getCount(), toRemove - removed), "How many?", game); + int amount = controller.getAmount(1, Math.min(permanent.getCounters(game).get(counterName).getCount(), toRemove - removed), "How many?", source, game); if (amount > 0) { removed += amount; permanent.removeCounters(counterName, amount, source, game); diff --git a/Mage.Sets/src/mage/cards/r/RenegadeRallier.java b/Mage.Sets/src/mage/cards/r/RenegadeRallier.java index 847bc6cb4fb..1569342b074 100644 --- a/Mage.Sets/src/mage/cards/r/RenegadeRallier.java +++ b/Mage.Sets/src/mage/cards/r/RenegadeRallier.java @@ -4,7 +4,6 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.condition.common.RevoltCondition; -import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -40,11 +39,9 @@ public final class RenegadeRallier extends CardImpl { // Revolt — When Renegade Rallier enters the battlefield, if a permanent you controlled left the battlefield this turn, // return target permanent card with converted mana cost 2 or less from your graveyard to your battlefield. - Ability ability = new ConditionalInterveningIfTriggeredAbility( - new EntersBattlefieldTriggeredAbility(new ReturnFromGraveyardToBattlefieldTargetEffect(), false), - RevoltCondition.instance, "When {this} enters, if a permanent you controlled " + - "left the battlefield this turn, return target permanent card with mana value 2 or less from your graveyard to the battlefield." - ).setAbilityWord(AbilityWord.REVOLT); + Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnFromGraveyardToBattlefieldTargetEffect()) + .withInterveningIf(RevoltCondition.instance); + ability.setAbilityWord(AbilityWord.REVOLT); ability.addTarget(new TargetCardInYourGraveyard(filter)); this.addAbility(ability.addHint(RevoltCondition.getHint()), new RevoltWatcher()); } diff --git a/Mage.Sets/src/mage/cards/r/RenoAndRude.java b/Mage.Sets/src/mage/cards/r/RenoAndRude.java new file mode 100644 index 00000000000..1332616b45e --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RenoAndRude.java @@ -0,0 +1,99 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.MenaceAbility; +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.players.Player; +import mage.util.CardUtil; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RenoAndRude extends CardImpl { + + public RenoAndRude(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ASSASSIN); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // Menace + this.addAbility(new MenaceAbility()); + + // Whenever Reno and Rude deals combat damage to a player, exile the top card of that player's library. Then you may sacrifice another creature or artifact. If you do, you may play the exiled card this turn, and mana of any type can be spent to cast it. + this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility( + new RenoAndRudeEffect(), false, true + )); + } + + private RenoAndRude(final RenoAndRude card) { + super(card); + } + + @Override + public RenoAndRude copy() { + return new RenoAndRude(this); + } +} + +class RenoAndRudeEffect extends OneShotEffect { + + RenoAndRudeEffect() { + super(Outcome.Benefit); + staticText = ", exile the top card of that player's library. " + + "Then you may sacrifice another creature or artifact. If you do, " + + "you may play the exiled card this turn, and mana of any type can be spent to cast it"; + } + + private RenoAndRudeEffect(final RenoAndRudeEffect effect) { + super(effect); + } + + @Override + public RenoAndRudeEffect copy() { + return new RenoAndRudeEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Card card = Optional + .ofNullable(getTargetPointer().getFirst(game, source)) + .map(game::getPlayer) + .map(Player::getLibrary) + .map(library -> library.getFromTop(game)) + .orElse(null); + Cost cost = new SacrificeTargetCost(StaticFilters.FILTER_CONTROLLED_ARTIFACT_OR_OTHER_CREATURE); + if (card == null) { + return cost.canPay(source, source, source.getControllerId(), game) + && player.chooseUse(Outcome.DrawCard, "Sacrifice another creature or artifact?", source, game) + && cost.pay(source, game, source, player.getId(), true); + } + player.moveCards(card, Zone.EXILED, source, game); + if (cost.canPay(source, source, source.getControllerId(), game) + && player.chooseUse(Outcome.DrawCard, "Sacrifice another creature or artifact?", source, game) + && cost.pay(source, game, source, player.getId(), true)) { + CardUtil.makeCardPlayable(game, source, card, false, Duration.EndOfTurn, true); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/r/Repentance.java b/Mage.Sets/src/mage/cards/r/Repentance.java index 92b43199dec..ce11757c467 100644 --- a/Mage.Sets/src/mage/cards/r/Repentance.java +++ b/Mage.Sets/src/mage/cards/r/Repentance.java @@ -1,7 +1,5 @@ - package mage.cards.r; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.cards.CardImpl; @@ -10,9 +8,10 @@ import mage.constants.CardType; import mage.constants.Outcome; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.players.Player; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + /** * * @author fireshoes @@ -55,12 +54,9 @@ class RepentanceEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - Permanent targetCreature = game.getPermanent(getTargetPointer().getFirst(game, source)); - if (targetCreature != null) { - targetCreature.damage(targetCreature.getPower().getValue(), source.getSourceId(), source, game, false, true); - } + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent != null) { + permanent.damage(permanent.getPower().getValue(), permanent.getId(), source, game, false, true); return true; } return false; diff --git a/Mage.Sets/src/mage/cards/r/Replenish.java b/Mage.Sets/src/mage/cards/r/Replenish.java index e897a642618..b04b7827618 100644 --- a/Mage.Sets/src/mage/cards/r/Replenish.java +++ b/Mage.Sets/src/mage/cards/r/Replenish.java @@ -4,7 +4,7 @@ import mage.abilities.effects.common.ReturnFromYourGraveyardToBattlefieldAllEffe import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.common.FilterEnchantmentCard; +import mage.filter.StaticFilters; import java.util.UUID; @@ -13,13 +13,11 @@ import java.util.UUID; */ public final class Replenish extends CardImpl { - private static final FilterEnchantmentCard filter = new FilterEnchantmentCard("enchantment cards"); - public Replenish(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{3}{W}"); + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{W}"); // Return all enchantment cards from your graveyard to the battlefield. - this.getSpellAbility().addEffect(new ReturnFromYourGraveyardToBattlefieldAllEffect(filter)); + this.getSpellAbility().addEffect(new ReturnFromYourGraveyardToBattlefieldAllEffect(StaticFilters.FILTER_CARD_ENCHANTMENTS)); } private Replenish(final Replenish card) { diff --git a/Mage.Sets/src/mage/cards/r/ResurgentBelief.java b/Mage.Sets/src/mage/cards/r/ResurgentBelief.java index 5c3523f8193..439bddb2693 100644 --- a/Mage.Sets/src/mage/cards/r/ResurgentBelief.java +++ b/Mage.Sets/src/mage/cards/r/ResurgentBelief.java @@ -6,7 +6,7 @@ import mage.abilities.keyword.SuspendAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.common.FilterEnchantmentCard; +import mage.filter.StaticFilters; import java.util.UUID; @@ -15,8 +15,6 @@ import java.util.UUID; */ public final class ResurgentBelief extends CardImpl { - private static final FilterEnchantmentCard filter = new FilterEnchantmentCard("enchantment cards"); - public ResurgentBelief(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, ""); @@ -26,7 +24,7 @@ public final class ResurgentBelief extends CardImpl { this.addAbility(new SuspendAbility(2, new ManaCostsImpl<>("{1}{W}"), this)); // Return all enchantment cards from your graveyard to the battlefield. - this.getSpellAbility().addEffect(new ReturnFromYourGraveyardToBattlefieldAllEffect(filter)); + this.getSpellAbility().addEffect(new ReturnFromYourGraveyardToBattlefieldAllEffect(StaticFilters.FILTER_CARD_ENCHANTMENTS)); } private ResurgentBelief(final ResurgentBelief card) { diff --git a/Mage.Sets/src/mage/cards/r/RetrieveTheEsper.java b/Mage.Sets/src/mage/cards/r/RetrieveTheEsper.java new file mode 100644 index 00000000000..e90a12a9a0b --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RetrieveTheEsper.java @@ -0,0 +1,78 @@ +package mage.cards.r; + +import mage.abilities.Ability; +import mage.abilities.condition.common.CastFromGraveyardSourceCondition; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.FlashbackAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.permanent.token.RobotBlueToken; +import mage.game.permanent.token.Token; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RetrieveTheEsper extends CardImpl { + + public RetrieveTheEsper(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{U}"); + + // Create a 3/3 blue Robot Warrior artifact creature token. Then if this spell was cast from a graveyard, put two +1/+1 counters on that token. + this.getSpellAbility().addEffect(new RetrieveTheEsperEffect()); + + // Flashback {5}{U} + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{5}{U}"))); + } + + private RetrieveTheEsper(final RetrieveTheEsper card) { + super(card); + } + + @Override + public RetrieveTheEsper copy() { + return new RetrieveTheEsper(this); + } +} + +class RetrieveTheEsperEffect extends OneShotEffect { + + RetrieveTheEsperEffect() { + super(Outcome.Benefit); + staticText = "create a 3/3 blue Robot Warrior artifact creature token. " + + "Then if this spell was cast from a graveyard, put two +1/+1 counters on that token"; + } + + private RetrieveTheEsperEffect(final RetrieveTheEsperEffect effect) { + super(effect); + } + + @Override + public RetrieveTheEsperEffect copy() { + return new RetrieveTheEsperEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Token token = new RobotBlueToken(); + token.putOntoBattlefield(1, game, source); + if (!CastFromGraveyardSourceCondition.instance.apply(game, source)) { + return true; + } + for (UUID tokenId : token.getLastAddedTokenIds()) { + Optional.ofNullable(tokenId) + .map(game::getPermanent) + .ifPresent(permanent -> permanent.addCounters( + CounterType.P1P1.createInstance(2), source, game + )); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/r/ReunionOfTheHouse.java b/Mage.Sets/src/mage/cards/r/ReunionOfTheHouse.java index 34f954a2b09..50f9f129c53 100644 --- a/Mage.Sets/src/mage/cards/r/ReunionOfTheHouse.java +++ b/Mage.Sets/src/mage/cards/r/ReunionOfTheHouse.java @@ -3,6 +3,7 @@ package mage.cards.r; import mage.MageInt; import mage.MageObject; import mage.abilities.Ability; +import mage.abilities.effects.common.ExileSpellEffect; import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -28,6 +29,7 @@ public final class ReunionOfTheHouse extends CardImpl { // Return any number of target creature cards with total power 10 or less from your graveyard to the battlefield. Exile Reunion of the House. this.getSpellAbility().addEffect(new ReturnFromGraveyardToBattlefieldTargetEffect()); this.getSpellAbility().addTarget(new ReunionOfTheHouseTarget()); + this.getSpellAbility().addEffect(new ExileSpellEffect()); } private ReunionOfTheHouse(final ReunionOfTheHouse card) { diff --git a/Mage.Sets/src/mage/cards/r/ReverberatingSummons.java b/Mage.Sets/src/mage/cards/r/ReverberatingSummons.java index 4ca6b91c46f..28ba609a62d 100644 --- a/Mage.Sets/src/mage/cards/r/ReverberatingSummons.java +++ b/Mage.Sets/src/mage/cards/r/ReverberatingSummons.java @@ -70,4 +70,9 @@ enum ReverberatingSummonsCondition implements Condition { .getWatcher(SpellsCastWatcher.class) .getCount(source.getControllerId()) >= 2; } + + @Override + public String toString() { + return "you've cast two or more spells this turn"; + } } diff --git a/Mage.Sets/src/mage/cards/r/RevivalOfTheAncestors.java b/Mage.Sets/src/mage/cards/r/RevivalOfTheAncestors.java index 9f73504a426..a706d550cb4 100644 --- a/Mage.Sets/src/mage/cards/r/RevivalOfTheAncestors.java +++ b/Mage.Sets/src/mage/cards/r/RevivalOfTheAncestors.java @@ -15,7 +15,7 @@ import mage.constants.SubType; import mage.counters.CounterType; import mage.filter.StaticFilters; import mage.game.permanent.token.NoFlyingSpiritWhiteToken; -import mage.target.common.TargetCreaturePermanentAmount; +import mage.target.common.TargetPermanentAmount; import java.util.UUID; @@ -42,7 +42,7 @@ public final class RevivalOfTheAncestors extends CardImpl { sagaAbility.addChapterEffect( this, SagaChapter.CHAPTER_II, new DistributeCountersEffect(CounterType.P1P1), - new TargetCreaturePermanentAmount(3) + new TargetPermanentAmount(3, 1, StaticFilters.FILTER_CONTROLLED_CREATURES) ); // III -- Creatures you control gain trample and lifelink until end of turn. diff --git a/Mage.Sets/src/mage/cards/r/RhetTombMystic.java b/Mage.Sets/src/mage/cards/r/RhetTombMystic.java index 0cd220bf0e7..8311930ace5 100644 --- a/Mage.Sets/src/mage/cards/r/RhetTombMystic.java +++ b/Mage.Sets/src/mage/cards/r/RhetTombMystic.java @@ -52,7 +52,7 @@ class RhetTombMysticEffect extends ContinuousEffectImpl { RhetTombMysticEffect() { super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); - this.staticText = "Each creature card in your hand has cycling {1}{B}"; + this.staticText = "Each creature card in your hand has cycling {1}{U}"; } private RhetTombMysticEffect(final RhetTombMysticEffect effect) { @@ -66,7 +66,7 @@ class RhetTombMysticEffect extends ContinuousEffectImpl { return false; controller.getHand().getCards(StaticFilters.FILTER_CARD_CREATURE, game) - .forEach(card -> game.getState().addOtherAbility(card, new CyclingAbility(new ManaCostsImpl<>("{1}{B}")))); + .forEach(card -> game.getState().addOtherAbility(card, new CyclingAbility(new ManaCostsImpl<>("{1}{U}")))); return true; } diff --git a/Mage.Sets/src/mage/cards/r/RidersOfTheMark.java b/Mage.Sets/src/mage/cards/r/RidersOfTheMark.java index beca0f7a2d2..deedcdd5fc3 100644 --- a/Mage.Sets/src/mage/cards/r/RidersOfTheMark.java +++ b/Mage.Sets/src/mage/cards/r/RidersOfTheMark.java @@ -2,23 +2,23 @@ package mage.cards.r; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.condition.common.AttackedThisTurnSourceCondition; -import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; -import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.AffinityEffect; import mage.abilities.effects.common.CreateTokenEffect; -import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; import mage.abilities.hint.Hint; import mage.abilities.hint.ValueHint; import mage.abilities.keyword.HasteAbility; import mage.abilities.keyword.TrampleAbility; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; -import mage.filter.FilterPermanent; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.Zone; import mage.filter.common.FilterControlledPermanent; import mage.game.Game; import mage.game.permanent.Permanent; @@ -31,10 +31,9 @@ import java.util.UUID; * @author Susucr */ public final class RidersOfTheMark extends CardImpl { - private static final FilterPermanent filter - = new FilterControlledPermanent(SubType.HUMAN, "Human you control"); - private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter); - private static final Hint hint = new ValueHint("Humans you control", xValue); + + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.HUMAN, "Humans"); + private static final Hint hint = new ValueHint("Humans you control", new PermanentsOnBattlefieldCount(filter)); public RidersOfTheMark(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{6}{R}"); @@ -45,11 +44,7 @@ public final class RidersOfTheMark extends CardImpl { this.toughness = new MageInt(4); // This spell costs {1} less to cast for each Human you control. - this.addAbility(new SimpleStaticAbility( - Zone.ALL, - new SpellCostReductionForEachSourceEffect(1, xValue) - .setCanWorksOnStackOnly(true) - ).setRuleAtTheTop(true).addHint(hint)); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new AffinityEffect(filter)).setRuleAtTheTop(true).addHint(hint)); // Trample this.addAbility(TrampleAbility.getInstance()); @@ -58,16 +53,8 @@ public final class RidersOfTheMark extends CardImpl { this.addAbility(HasteAbility.getInstance()); // At the beginning of your end step, if Riders of the Mark attacked this turn, return it to its owner's hand. If you do, create a number of 1/1 white Human Soldier creature tokens equal to its toughness. - this.addAbility(new ConditionalInterveningIfTriggeredAbility( - new BeginningOfEndStepTriggeredAbility( - new RidersOfTheMarkEffect() - ), - AttackedThisTurnSourceCondition.instance, - "At the beginning of your end step, if {this} attacked this turn, " - + "return it to its owner's hand. If you do, create a number of " - + "1/1 white Human Soldier creature tokens equal to its toughness." - )); - + this.addAbility(new BeginningOfEndStepTriggeredAbility(new RidersOfTheMarkEffect()) + .withInterveningIf(AttackedThisTurnSourceCondition.instance)); } private RidersOfTheMark(final RidersOfTheMark card) { diff --git a/Mage.Sets/src/mage/cards/r/RinoaHeartilly.java b/Mage.Sets/src/mage/cards/r/RinoaHeartilly.java new file mode 100644 index 00000000000..fe7bbf4609c --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RinoaHeartilly.java @@ -0,0 +1,56 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.dynamicvalue.common.CreaturesYouControlCount; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.hint.common.CreaturesYouControlHint; +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.AngeloToken; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RinoaHeartilly extends CardImpl { + + public RinoaHeartilly(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.REBEL); + this.subtype.add(SubType.WARLOCK); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // When Rinoa Heartilly enters, create Angelo, a legendary 1/1 green and white Dog creature token. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new AngeloToken()))); + + // Angelo Cannon -- Whenever Rinoa Heartilly attacks, another target creature you control gets +1/+1 until end of turn for each creature you control. + Ability ability = new AttacksTriggeredAbility(new BoostTargetEffect( + CreaturesYouControlCount.instance, CreaturesYouControlCount.instance + ).setText("another target creature you control gets +1/+1 until end of turn for each creature you control")); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_ANOTHER_TARGET_CREATURE_YOU_CONTROL)); + this.addAbility(ability.withFlavorWord("Angelo Cannon").addHint(CreaturesYouControlHint.instance)); + } + + private RinoaHeartilly(final RinoaHeartilly card) { + super(card); + } + + @Override + public RinoaHeartilly copy() { + return new RinoaHeartilly(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RiseOfTheHobgoblins.java b/Mage.Sets/src/mage/cards/r/RiseOfTheHobgoblins.java index a425674fae7..d1a4c2bacd8 100644 --- a/Mage.Sets/src/mage/cards/r/RiseOfTheHobgoblins.java +++ b/Mage.Sets/src/mage/cards/r/RiseOfTheHobgoblins.java @@ -19,7 +19,6 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Outcome; import mage.constants.TargetController; -import mage.constants.Zone; import mage.filter.FilterPermanent; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.ColorPredicate; @@ -87,7 +86,7 @@ class RiseOfTheHobgoblinsEffect extends OneShotEffect { Player you = game.getPlayer(source.getControllerId()); ManaCosts cost = new ManaCostsImpl<>("{X}"); if (you != null && you.chooseUse(Outcome.Neutral, "Do you want to to pay {X}?", source, game)) { - int costX = you.announceXMana(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source); + int costX = you.announceX(0, Integer.MAX_VALUE, "Announce the value for {X} (pay to add counters)", game, source, true); cost.add(new GenericManaCost(costX)); if (cost.pay(source, game, source, source.getControllerId(), false, null)) { Token token = new GoblinSoldierToken(); diff --git a/Mage.Sets/src/mage/cards/r/RiteOfRenewal.java b/Mage.Sets/src/mage/cards/r/RiteOfRenewal.java index cfc8ade8917..d4e695db1a6 100644 --- a/Mage.Sets/src/mage/cards/r/RiteOfRenewal.java +++ b/Mage.Sets/src/mage/cards/r/RiteOfRenewal.java @@ -1,7 +1,7 @@ package mage.cards.r; import mage.abilities.effects.common.ExileSpellEffect; -import mage.abilities.effects.common.ReturnToHandTargetEffect; +import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect; import mage.abilities.effects.common.TargetPlayerShufflesTargetCardsEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -22,13 +22,13 @@ public final class RiteOfRenewal extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{G}"); // Return up to two target permanent cards from your graveyard to your hand. Target player shuffles up to four target cards from their graveyard into their library. Exile Rite of Renewal. - this.getSpellAbility().addEffect(new ReturnToHandTargetEffect()); + this.getSpellAbility().addEffect(new ReturnFromGraveyardToHandTargetEffect()); this.getSpellAbility().addTarget(new TargetCardInYourGraveyard( 0, 2, StaticFilters.FILTER_CARD_PERMANENTS )); this.getSpellAbility().addEffect(new TargetPlayerShufflesTargetCardsEffect(1)); this.getSpellAbility().addTarget(new TargetPlayer()); - this.getSpellAbility().addTarget(new TargetCardInTargetPlayersGraveyard(3, 1)); + this.getSpellAbility().addTarget(new TargetCardInTargetPlayersGraveyard(4, 1)); this.getSpellAbility().addEffect(new ExileSpellEffect()); } diff --git a/Mage.Sets/src/mage/cards/r/RitesOfInitiation.java b/Mage.Sets/src/mage/cards/r/RitesOfInitiation.java index 1aa6a77b0cd..f65979a5258 100644 --- a/Mage.Sets/src/mage/cards/r/RitesOfInitiation.java +++ b/Mage.Sets/src/mage/cards/r/RitesOfInitiation.java @@ -56,7 +56,7 @@ class RitesOfInitiationEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); if (player != null) { - int numToDiscard = player.getAmount(0, player.getHand().size(), "Discard how many cards at random?", game); + int numToDiscard = player.getAmount(0, player.getHand().size(), "Discard how many cards at random?", source, game); player.discard(numToDiscard, true, false, source, game); game.addEffect(new BoostControlledEffect(numToDiscard, 0, Duration.EndOfTurn), source); return true; diff --git a/Mage.Sets/src/mage/cards/r/RiverKelpie.java b/Mage.Sets/src/mage/cards/r/RiverKelpie.java index d7139f73010..7c194aee656 100644 --- a/Mage.Sets/src/mage/cards/r/RiverKelpie.java +++ b/Mage.Sets/src/mage/cards/r/RiverKelpie.java @@ -76,7 +76,7 @@ class RiverKelpieTriggeredAbility extends TriggeredAbilityImpl { @Override public String getRule() { - return "Whenever {this} or another permanent enters the battlefield from a graveyard, draw a card."; + return "Whenever {this} or another permanent enters from a graveyard, draw a card."; } } diff --git a/Mage.Sets/src/mage/cards/r/RomanaII.java b/Mage.Sets/src/mage/cards/r/RomanaII.java index e35cc3decbe..70361945787 100644 --- a/Mage.Sets/src/mage/cards/r/RomanaII.java +++ b/Mage.Sets/src/mage/cards/r/RomanaII.java @@ -25,7 +25,7 @@ import java.util.UUID; */ public final class RomanaII extends CardImpl { - private static final FilterPermanent filter = new FilterPermanent("token that entered the battlefield this turn"); + private static final FilterPermanent filter = new FilterPermanent("token that entered this turn"); static { filter.add(TokenPredicate.TRUE); diff --git a/Mage.Sets/src/mage/cards/r/RosaResoluteWhiteMage.java b/Mage.Sets/src/mage/cards/r/RosaResoluteWhiteMage.java new file mode 100644 index 00000000000..7d90f965cb8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RosaResoluteWhiteMage.java @@ -0,0 +1,53 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.keyword.LifelinkAbility; +import mage.abilities.keyword.ReachAbility; +import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; +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.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RosaResoluteWhiteMage extends CardImpl { + + public RosaResoluteWhiteMage(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.NOBLE); + this.subtype.add(SubType.CLERIC); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // At the beginning of combat on your turn, put a +1/+1 counter on target creature you control. It gains lifelink until end of turn. + Ability ability = new BeginningOfCombatTriggeredAbility(new AddCountersTargetEffect(CounterType.P1P1.createInstance())); + ability.addEffect(new GainAbilityTargetEffect(LifelinkAbility.getInstance()).setText("It gains lifelink until end of turn")); + ability.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(ability); + } + + private RosaResoluteWhiteMage(final RosaResoluteWhiteMage card) { + super(card); + } + + @Override + public RosaResoluteWhiteMage copy() { + return new RosaResoluteWhiteMage(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RoseRoomTreasurer.java b/Mage.Sets/src/mage/cards/r/RoseRoomTreasurer.java index eaae8b13b14..bc18ac0f02e 100644 --- a/Mage.Sets/src/mage/cards/r/RoseRoomTreasurer.java +++ b/Mage.Sets/src/mage/cards/r/RoseRoomTreasurer.java @@ -80,7 +80,7 @@ class RoseRoomTreasurerEffect extends OneShotEffect { if (!player.chooseUse(Outcome.BoostCreature, "Pay {X}?", source, game)) { return false; } - int costX = player.announceXMana(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source); + int costX = player.announceX(0, Integer.MAX_VALUE, "Announce the value for {X} (pay to damage)", game, source, true); cost.add(new GenericManaCost(costX)); if (!cost.pay(source, game, source, source.getControllerId(), false)) { return false; diff --git a/Mage.Sets/src/mage/cards/r/RunescaleStormbrood.java b/Mage.Sets/src/mage/cards/r/RunescaleStormbrood.java index bb106ea73e4..cb39af52c3d 100644 --- a/Mage.Sets/src/mage/cards/r/RunescaleStormbrood.java +++ b/Mage.Sets/src/mage/cards/r/RunescaleStormbrood.java @@ -24,7 +24,7 @@ import mage.target.TargetSpell; public final class RunescaleStormbrood extends OmenCard { private static final FilterSpell filter = new FilterSpell("spell with mana value 2 or less"); - private static final FilterSpell castFilter = new FilterSpell("noncreature spell or Dragon spell"); + private static final FilterSpell castFilter = new FilterSpell("a noncreature spell or a Dragon spell"); static { filter.add(new ManaValuePredicate(ComparisonType.OR_LESS, 2)); diff --git a/Mage.Sets/src/mage/cards/r/Rupture.java b/Mage.Sets/src/mage/cards/r/Rupture.java index c322b10d4e2..3b495306b74 100644 --- a/Mage.Sets/src/mage/cards/r/Rupture.java +++ b/Mage.Sets/src/mage/cards/r/Rupture.java @@ -65,10 +65,7 @@ class RuptureEffect extends OneShotEffect { if (player != null) { int power = 0; TargetSacrifice target = new TargetSacrifice(StaticFilters.FILTER_PERMANENT_CREATURE); - if (target.canChoose(player.getId(), source, game)) { - while (!target.isChosen(game) && target.canChoose(player.getId(), source, game) && player.canRespond()) { - player.choose(Outcome.Sacrifice, target, source, game); - } + if (player.choose(Outcome.Sacrifice, target, source, game)){ Permanent permanent = game.getPermanent(target.getFirstTarget()); if (permanent != null) { power = permanent.getPower().getValue(); diff --git a/Mage.Sets/src/mage/cards/r/RyanSinclair.java b/Mage.Sets/src/mage/cards/r/RyanSinclair.java new file mode 100644 index 00000000000..ee997f3e9dc --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RyanSinclair.java @@ -0,0 +1,103 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.DoctorsCompanionAbility; +import mage.cards.*; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.predicate.mageobject.ManaValuePredicate; +import mage.game.Game; +import mage.players.Player; +import mage.util.CardUtil; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RyanSinclair extends CardImpl { + + public RyanSinclair(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Whenever Ryan attacks, exile cards from the top of your library until you exile a nonland card. You may cast the exiled card without paying its mana cost if it's a spell with mana value less than or equal to Ryan's power. Put the exiled cards not cast this way on the bottom of your library in a random order. + this.addAbility(new AttacksTriggeredAbility(new RyanSinclairEffect())); + + // Doctor's companion + this.addAbility(DoctorsCompanionAbility.getInstance()); + } + + private RyanSinclair(final RyanSinclair card) { + super(card); + } + + @Override + public RyanSinclair copy() { + return new RyanSinclair(this); + } +} + +class RyanSinclairEffect extends OneShotEffect { + + RyanSinclairEffect() { + super(Outcome.Benefit); + staticText = "exile cards from the top of your library until you exile a nonland card. " + + "You may cast the exiled card without paying its mana cost if it's a spell " + + "with mana value less than or equal to {this}'s power. Put the exiled cards " + + "not cast this way on the bottom of your library in a random order"; + } + + private RyanSinclairEffect(final RyanSinclairEffect effect) { + super(effect); + } + + @Override + public RyanSinclairEffect copy() { + return new RyanSinclairEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Cards cards = new CardsImpl(); + Card card = getCard(player, cards, source, game); + if (card != null) { + FilterCard filter = new FilterCard(); + filter.add(new ManaValuePredicate( + ComparisonType.FEWER_THAN, + Optional.ofNullable(source.getSourcePermanentOrLKI(game)) + .map(MageObject::getPower) + .map(MageInt::getValue) + .orElse(-1) + )); + CardUtil.castSpellWithAttributesForFree(player, source, game, card, filter); + } + cards.retainZone(Zone.EXILED, game); + player.putCardsOnBottomOfLibrary(cards, game, source, false); + return true; + } + + private static Card getCard(Player player, Cards cards, Ability source, Game game) { + for (Card card : player.getLibrary().getCards(game)) { + cards.add(card); + player.moveCards(card, Zone.EXILED, source, game); + if (!card.isLand(game)) { + return card; + } + } + return null; + } +} diff --git a/Mage.Sets/src/mage/cards/s/SOLDIERMilitaryProgram.java b/Mage.Sets/src/mage/cards/s/SOLDIERMilitaryProgram.java new file mode 100644 index 00000000000..3ce4752aedc --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SOLDIERMilitaryProgram.java @@ -0,0 +1,50 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.condition.common.ControlACommanderCondition; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.game.permanent.token.SoldierToken; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SOLDIERMilitaryProgram extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledPermanent(SubType.SOLDIER, "Soldiers you control"); + + public SOLDIERMilitaryProgram(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{W}"); + + // At the beginning of combat on your turn, choose one. If you control a commander, you may choose both instead. + // * Create a 1/1 white Soldier creature token. + Ability ability = new BeginningOfCombatTriggeredAbility(new CreateTokenEffect(new SoldierToken())); + ability.getModes().setMoreCondition(2, ControlACommanderCondition.instance); + + // * Put a +1/+1 counter on each of up to two Soldiers you control. + ability.addMode(new Mode(new AddCountersTargetEffect(CounterType.P1P1.createInstance())) + .addTarget(new TargetPermanent(0, 2, filter))); + this.addAbility(ability); + } + + private SOLDIERMilitaryProgram(final SOLDIERMilitaryProgram card) { + super(card); + } + + @Override + public SOLDIERMilitaryProgram copy() { + return new SOLDIERMilitaryProgram(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SabinMasterMonk.java b/Mage.Sets/src/mage/cards/s/SabinMasterMonk.java new file mode 100644 index 00000000000..e2eaad1c80a --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SabinMasterMonk.java @@ -0,0 +1,88 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.common.DiscardCardCost; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.keyword.BlitzAbility; +import mage.abilities.keyword.DoubleStrikeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SabinMasterMonk extends CardImpl { + + public SabinMasterMonk(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.NOBLE); + this.subtype.add(SubType.MONK); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // Double strike + this.addAbility(DoubleStrikeAbility.getInstance()); + + // Blitz--{2}{R}{R}, Discard a card. + Ability ability = new BlitzAbility(this, "{2}{R}{R}"); + ability.addCost(new DiscardCardCost()); + this.addAbility(ability); + + // You may cast this card from your graveyard using its blitz ability. + this.addAbility(new SimpleStaticAbility(Zone.ALL, new SabinMasterMonkEffect())); + } + + private SabinMasterMonk(final SabinMasterMonk card) { + super(card); + } + + @Override + public SabinMasterMonk copy() { + return new SabinMasterMonk(this); + } +} + +class SabinMasterMonkEffect extends AsThoughEffectImpl { + + SabinMasterMonkEffect() { + super(AsThoughEffectType.CAST_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfGame, Outcome.PutCreatureInPlay); + staticText = "you may cast this card from your graveyard using its blitz ability"; + } + + private SabinMasterMonkEffect(final SabinMasterMonkEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public SabinMasterMonkEffect copy() { + return new SabinMasterMonkEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) { + return objectId.equals(source.getSourceId()) + && source.isControlledBy(playerId) + && affectedAbility instanceof BlitzAbility + && game.getState().getZone(source.getSourceId()) == Zone.GRAVEYARD + && game.getCard(source.getSourceId()) != null; + } + + @Override + public boolean applies(UUID sourceId, Ability source, UUID affectedControllerId, Game game) { + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/s/SadisticShellGame.java b/Mage.Sets/src/mage/cards/s/SadisticShellGame.java new file mode 100644 index 00000000000..2106ff2cf1a --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SadisticShellGame.java @@ -0,0 +1,105 @@ +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.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.permanent.ControllerIdPredicate; +import mage.game.Controllable; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author TheElk801 + */ +public final class SadisticShellGame extends CardImpl { + + public SadisticShellGame(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{B}"); + + // Starting with the next opponent in turn order, each player chooses a creature you don't control. Destroy the chosen creatures. + this.getSpellAbility().addEffect(new SadisticShellGameEffect()); + } + + private SadisticShellGame(final SadisticShellGame card) { + super(card); + } + + @Override + public SadisticShellGame copy() { + return new SadisticShellGame(this); + } +} + +class SadisticShellGameEffect extends OneShotEffect { + + SadisticShellGameEffect() { + super(Outcome.Benefit); + staticText = "starting with the next opponent in turn order, " + + "each player chooses a creature you don't control. Destroy the chosen creatures"; + } + + private SadisticShellGameEffect(final SadisticShellGameEffect effect) { + super(effect); + } + + @Override + public SadisticShellGameEffect copy() { + return new SadisticShellGameEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + FilterPermanent filter = new FilterCreaturePermanent( + "creature not controlled by " + + Optional.ofNullable(source) + .map(Controllable::getControllerId) + .map(game::getPlayer) + .map(Player::getName) + .orElse("this spell's controller") + ); + filter.add(Predicates.not(new ControllerIdPredicate(source.getControllerId()))); + if (!game.getBattlefield().contains(filter, source, game, 1)) { + return false; + } + Set permanents = new HashSet<>(); + List playerIds = game + .getState() + .getPlayersInRange(source.getControllerId(), game) + .stream() + .distinct() + .collect(Collectors.toList()); + playerIds.remove(source.getControllerId()); + playerIds.add(source.getControllerId()); + TargetPermanent target = new TargetPermanent(filter); + target.withNotTarget(true); + for (UUID playerId : playerIds) { + Player player = game.getPlayer(playerId); + if (player == null) { + continue; + } + target.clearChosen(); + player.choose(Outcome.DestroyPermanent, target, source, game); + Permanent permanent = game.getPermanent(target.getFirstTarget()); + if (permanent == null) { + continue; + } + game.informPlayers(player.getLogName() + " chooses to destroy " + permanent.getLogName()); + permanents.add(permanent); + } + for (Permanent permanent : permanents) { + permanent.destroy(source, game); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/s/SagesNouliths.java b/Mage.Sets/src/mage/cards/s/SagesNouliths.java new file mode 100644 index 00000000000..20b2a425bd9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SagesNouliths.java @@ -0,0 +1,59 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.TriggeredAbility; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.UntapTargetEffect; +import mage.abilities.effects.common.continuous.AddCardSubtypeAttachedEffect; +import mage.abilities.effects.common.continuous.BoostEquippedEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.keyword.EquipAbility; +import mage.abilities.keyword.JobSelectAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AttachmentType; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetAttackingCreature; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SagesNouliths extends CardImpl { + + public SagesNouliths(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{U}"); + + this.subtype.add(SubType.EQUIPMENT); + + // Job select + this.addAbility(new JobSelectAbility()); + + // Equipped creature gets +1/+0, has "Whenever this creature attacks, untap target attacking creature," and is a Cleric in addition to its other types. + Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(1, 0)); + TriggeredAbility triggeredAbility = new AttacksTriggeredAbility(new UntapTargetEffect()); + triggeredAbility.addTarget(new TargetAttackingCreature()); + ability.addEffect(new GainAbilityAttachedEffect( + triggeredAbility, AttachmentType.EQUIPMENT + ).setText("has \"Whenever this creature attacks, untap target attacking creature,\"")); + ability.addEffect(new AddCardSubtypeAttachedEffect( + SubType.CLERIC, AttachmentType.EQUIPMENT + ).setText("and is a Cleric in addition to its other types")); + this.addAbility(ability); + + // Hagneia -- Equip {3} + this.addAbility(new EquipAbility(3).withFlavorWord("Hagneia")); + } + + private SagesNouliths(final SagesNouliths card) { + super(card); + } + + @Override + public SagesNouliths copy() { + return new SagesNouliths(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/Sahagin.java b/Mage.Sets/src/mage/cards/s/Sahagin.java new file mode 100644 index 00000000000..95f145ce811 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/Sahagin.java @@ -0,0 +1,48 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.common.combat.CantBeBlockedSourceEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Sahagin extends CardImpl { + + public Sahagin(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}"); + + this.subtype.add(SubType.MERFOLK); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(1); + this.toughness = new MageInt(3); + + // Whenever you cast a noncreature spell, if at least four mana was spent to cast it, put a +1/+1 counter on this creature and it can't be blocked this turn. + Ability ability = new SpellCastControllerTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), + StaticFilters.FILTER_NONCREATURE_SPELL_FOUR_MANA_SPENT, false + ); + ability.addEffect(new CantBeBlockedSourceEffect(Duration.EndOfTurn).setText("and it can't be blocked this turn")); + this.addAbility(ability); + } + + private Sahagin(final Sahagin card) { + super(card); + } + + @Override + public Sahagin copy() { + return new Sahagin(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SaltRoadSkirmish.java b/Mage.Sets/src/mage/cards/s/SaltRoadSkirmish.java index 5f8189291b5..504bdfd698e 100644 --- a/Mage.Sets/src/mage/cards/s/SaltRoadSkirmish.java +++ b/Mage.Sets/src/mage/cards/s/SaltRoadSkirmish.java @@ -47,7 +47,8 @@ class SaltRoadSkirmishEffect extends OneShotEffect { SaltRoadSkirmishEffect() { super(Outcome.Benefit); - staticText = ""; + staticText = "create two 1/1 red Warrior creature tokens. " + + "They gain haste until end of turn. Sacrifice them at the beginning of the next end step"; } private SaltRoadSkirmishEffect(final SaltRoadSkirmishEffect effect) { diff --git a/Mage.Sets/src/mage/cards/s/SamuraisKatana.java b/Mage.Sets/src/mage/cards/s/SamuraisKatana.java new file mode 100644 index 00000000000..157e3cc9fc1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SamuraisKatana.java @@ -0,0 +1,58 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.AddCardSubtypeAttachedEffect; +import mage.abilities.effects.common.continuous.BoostEquippedEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.keyword.EquipAbility; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.JobSelectAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AttachmentType; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SamuraisKatana extends CardImpl { + + public SamuraisKatana(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{R}"); + + this.subtype.add(SubType.EQUIPMENT); + + // Job select + this.addAbility(new JobSelectAbility()); + + // Equipped creature gets +2/+2, has trample and haste, and is a Samurai in addition to its other types. + Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(2, 2)); + ability.addEffect(new GainAbilityAttachedEffect( + TrampleAbility.getInstance(), AttachmentType.EQUIPMENT + ).setText(", has trample")); + ability.addEffect(new GainAbilityAttachedEffect( + HasteAbility.getInstance(), AttachmentType.EQUIPMENT + ).setText("and haste")); + ability.addEffect(new AddCardSubtypeAttachedEffect( + SubType.SAMURAI, AttachmentType.EQUIPMENT + ).setText(", and is a Samurai in addition to its other types")); + this.addAbility(ability); + + // Murasame -- Equip {5} + this.addAbility(new EquipAbility(5).withFlavorWord("Murasame")); + } + + private SamuraisKatana(final SamuraisKatana card) { + super(card); + } + + @Override + public SamuraisKatana copy() { + return new SamuraisKatana(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SanctumPrelate.java b/Mage.Sets/src/mage/cards/s/SanctumPrelate.java index 04249ece695..b98a522b910 100644 --- a/Mage.Sets/src/mage/cards/s/SanctumPrelate.java +++ b/Mage.Sets/src/mage/cards/s/SanctumPrelate.java @@ -62,7 +62,7 @@ class ChooseNumberEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { - int numberChoice = controller.announceXMana(0, Integer.MAX_VALUE, "Choose a number.", game, source); + int numberChoice = controller.getAmount(0, Integer.MAX_VALUE, "Choose a number (mana cost to restrict)", source, game); game.getState().setValue(source.getSourceId().toString(), numberChoice); Permanent permanent = game.getPermanentEntering(source.getSourceId()); diff --git a/Mage.Sets/src/mage/cards/s/SavageBeating.java b/Mage.Sets/src/mage/cards/s/SavageBeating.java index 1f59ccc5fc8..9a59b5fdb04 100644 --- a/Mage.Sets/src/mage/cards/s/SavageBeating.java +++ b/Mage.Sets/src/mage/cards/s/SavageBeating.java @@ -28,7 +28,7 @@ public final class SavageBeating extends CardImpl { // Cast Savage Beating only during your turn and only during combat. this.addAbility(new CastOnlyDuringPhaseStepSourceAbility(TurnPhase.COMBAT, null, MyTurnCondition.instance, - "Cast this spell only during your turn and only during combat")); + "Cast this spell only during combat on your turn")); // Choose one - Creatures you control gain double strike until end of turn; this.getSpellAbility().addEffect(new GainAbilityControlledEffect(DoubleStrikeAbility.getInstance(), Duration.EndOfTurn, StaticFilters.FILTER_PERMANENT_CREATURES, false)); diff --git a/Mage.Sets/src/mage/cards/s/SazhKatzroy.java b/Mage.Sets/src/mage/cards/s/SazhKatzroy.java new file mode 100644 index 00000000000..e1af5896abb --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SazhKatzroy.java @@ -0,0 +1,68 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.DoubleCountersTargetEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +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.counters.CounterType; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicates; +import mage.target.common.TargetCardInLibrary; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SazhKatzroy extends CardImpl { + + private static final FilterCard filter = new FilterCard("Bird or basic land card"); + + static { + filter.add(Predicates.or( + SubType.BIRD.getPredicate(), + Predicates.and( + SuperType.BASIC.getPredicate(), + CardType.LAND.getPredicate() + ) + )); + } + + public SazhKatzroy(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.PILOT); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // When Sazh Katzroy enters, you may search your library for a Bird or basic land card, reveal it, put it into your hand, then shuffle. + this.addAbility(new EntersBattlefieldTriggeredAbility( + new SearchLibraryPutInHandEffect(new TargetCardInLibrary(filter), true) + )); + + // Whenever Sazh Katzroy attacks, put counter on target creature, then double the number of +1/+1 counters on that creature. + Ability ability = new AttacksTriggeredAbility(new AddCountersTargetEffect(CounterType.P1P1.createInstance())); + ability.addEffect(new DoubleCountersTargetEffect(CounterType.P1P1) + .setText(", then double the number of +1/+1 counters on that creature")); + this.addAbility(ability); + } + + private SazhKatzroy(final SazhKatzroy card) { + super(card); + } + + @Override + public SazhKatzroy copy() { + return new SazhKatzroy(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/ScalesOfShale.java b/Mage.Sets/src/mage/cards/s/ScalesOfShale.java index 3bd95e41204..305fffac10e 100644 --- a/Mage.Sets/src/mage/cards/s/ScalesOfShale.java +++ b/Mage.Sets/src/mage/cards/s/ScalesOfShale.java @@ -1,11 +1,10 @@ package mage.cards.s; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.AffinityEffect; import mage.abilities.effects.common.continuous.BoostTargetEffect; import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; -import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; import mage.abilities.hint.Hint; import mage.abilities.hint.ValueHint; import mage.abilities.keyword.IndestructibleAbility; @@ -26,20 +25,14 @@ import java.util.UUID; */ public final class ScalesOfShale extends CardImpl { - private static final DynamicValue xValue = new PermanentsOnBattlefieldCount( - new FilterControlledPermanent(SubType.LIZARD, "Lizard you control") - ); - private static final Hint hint = new ValueHint("Lizards you control", xValue); + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.LIZARD, "Lizards"); + private static final Hint hint = new ValueHint("Lizards you control", new PermanentsOnBattlefieldCount(filter)); public ScalesOfShale(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{B}"); // This spell costs {1} less to cast for each Lizard you control. - this.addAbility(new SimpleStaticAbility( - Zone.ALL, - new SpellCostReductionForEachSourceEffect(1, xValue).setCanWorksOnStackOnly(true) - ).setRuleAtTheTop(true).addHint(hint)); - + this.addAbility(new SimpleStaticAbility(Zone.ALL, new AffinityEffect(filter)).setRuleAtTheTop(true).addHint(hint)); // Target creature gets +2/+0 and gains lifelink and indestructible until end of turn. this.getSpellAbility().addEffect(new BoostTargetEffect( diff --git a/Mage.Sets/src/mage/cards/s/Scapeshift.java b/Mage.Sets/src/mage/cards/s/Scapeshift.java index a5fa6db6e9d..9989c1a1960 100644 --- a/Mage.Sets/src/mage/cards/s/Scapeshift.java +++ b/Mage.Sets/src/mage/cards/s/Scapeshift.java @@ -67,6 +67,8 @@ class ScapeshiftEffect extends OneShotEffect { } int amount = 0; TargetSacrifice sacrificeLand = new TargetSacrifice(0, Integer.MAX_VALUE, StaticFilters.FILTER_LANDS); + // TODO: replace example for #8254: + // sacrificeLand.choose(Outcome.Sacrifice, controller.getId(), source.getSourceId(), source, game) if (controller.choose(Outcome.Sacrifice, sacrificeLand, source, game)) { for (UUID uuid : sacrificeLand.getTargets()) { Permanent land = game.getPermanent(uuid); diff --git a/Mage.Sets/src/mage/cards/s/ScroungingBandar.java b/Mage.Sets/src/mage/cards/s/ScroungingBandar.java index 2f0a7a50c9d..6d9fb216110 100644 --- a/Mage.Sets/src/mage/cards/s/ScroungingBandar.java +++ b/Mage.Sets/src/mage/cards/s/ScroungingBandar.java @@ -78,7 +78,7 @@ class ScroungingBandarEffect extends OneShotEffect { if (fromPermanent != null && toPermanent != null) { int amountCounters = fromPermanent.getCounters(game).getCount(CounterType.P1P1); if (amountCounters > 0) { - int amountToMove = controller.getAmount(0, amountCounters, "How many counters do you want to move?", game); + int amountToMove = controller.getAmount(0, amountCounters, "How many counters do you want to move?", source, game); if (amountToMove > 0) { fromPermanent.removeCounters(CounterType.P1P1.createInstance(amountToMove), source, game); toPermanent.addCounters(CounterType.P1P1.createInstance(amountToMove), source.getControllerId(), source, game); diff --git a/Mage.Sets/src/mage/cards/s/ScryingGlass.java b/Mage.Sets/src/mage/cards/s/ScryingGlass.java index edd9daa8369..65b680f1f6e 100644 --- a/Mage.Sets/src/mage/cards/s/ScryingGlass.java +++ b/Mage.Sets/src/mage/cards/s/ScryingGlass.java @@ -64,7 +64,7 @@ class ScryingGlassEffect extends OneShotEffect { int amount = 0; if (controller != null && targetOpponent != null) { - amount = controller.getAmount(1, Integer.MAX_VALUE, "Choose a number", game); + amount = controller.getAmount(1, Integer.MAX_VALUE, "Choose a number", source, game); controller.choose(Outcome.Discard, color, game); FilterCard filter = new FilterCard(); filter.add(new ColorPredicate(color.getColor())); diff --git a/Mage.Sets/src/mage/cards/s/ScytheOfTheWretched.java b/Mage.Sets/src/mage/cards/s/ScytheOfTheWretched.java index 71a8c01de33..8292093b41f 100644 --- a/Mage.Sets/src/mage/cards/s/ScytheOfTheWretched.java +++ b/Mage.Sets/src/mage/cards/s/ScytheOfTheWretched.java @@ -13,6 +13,7 @@ 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.players.Player; import mage.target.targetpointer.FixedTarget; @@ -34,7 +35,8 @@ public final class ScytheOfTheWretched extends CardImpl { this.addAbility(new SimpleStaticAbility(new BoostEquippedEffect(2, 2, Duration.WhileOnBattlefield))); // Whenever a creature dealt damage by equipped creature this turn dies, return that card to the battlefield under your control. Attach Scythe of the Wretched to that creature. - this.addAbility(new DealtDamageAttachedAndDiedTriggeredAbility(new ScytheOfTheWretchedReanimateEffect(), false)); + this.addAbility(new DealtDamageAttachedAndDiedTriggeredAbility(new ScytheOfTheWretchedReanimateEffect(), false, + StaticFilters.FILTER_PERMANENT_CREATURE, SetTargetPointer.CARD, AttachmentType.EQUIPMENT)); // Equip {4} this.addAbility(new EquipAbility(Outcome.AddAbility, new GenericManaCost(4), false)); diff --git a/Mage.Sets/src/mage/cards/s/SelfDestruct.java b/Mage.Sets/src/mage/cards/s/SelfDestruct.java new file mode 100644 index 00000000000..9c563c32492 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SelfDestruct.java @@ -0,0 +1,95 @@ +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.common.FilterAnyTarget; +import mage.filter.predicate.other.AnotherTargetPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetAnyTarget; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.targetpointer.EachTargetPointer; + +import java.util.List; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SelfDestruct extends CardImpl { + + private static final FilterAnyTarget filter = new FilterAnyTarget("any other target"); + + static { + filter.getPermanentFilter().add(new AnotherTargetPredicate(2)); + filter.getPlayerFilter().add(new AnotherTargetPredicate(2)); + } + + public SelfDestruct(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{R}"); + + // Target creature you control deals X damage to any other target and X damage to itself, where X is its power. + this.getSpellAbility().addEffect(new SelfDestructEffect()); + this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent()); + this.getSpellAbility().addTarget(new TargetAnyTarget().setTargetTag(2)); + } + + private SelfDestruct(final SelfDestruct card) { + super(card); + } + + @Override + public SelfDestruct copy() { + return new SelfDestruct(this); + } +} + +class SelfDestructEffect extends OneShotEffect { + + SelfDestructEffect() { + super(Outcome.Benefit); + staticText = "target creature you control deals X damage " + + "to any other target and X damage to itself, where X is its power"; + this.setTargetPointer(new EachTargetPointer()); + } + + private SelfDestructEffect(final SelfDestructEffect effect) { + super(effect); + } + + @Override + public SelfDestructEffect copy() { + return new SelfDestructEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + List targets = this.getTargetPointer().getTargets(game, source); + if (targets.size() < 2) { + return false; + } + Permanent creature = game.getPermanent(targets.get(0)); + if (creature == null) { + return false; + } + int power = creature.getPower().getValue(); + if (power < 1) { + return false; + } + Permanent permanent = game.getPermanent(targets.get(1)); + if (permanent != null) { + permanent.damage(power, creature.getId(), source, game); + } + Player player = game.getPlayer(targets.get(1)); + if (player != null) { + player.damage(power, creature.getId(), source, game); + } + permanent.damage(power, permanent.getId(), source, game); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/s/SephirothFabledSOLDIER.java b/Mage.Sets/src/mage/cards/s/SephirothFabledSOLDIER.java new file mode 100644 index 00000000000..2ce8c407227 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SephirothFabledSOLDIER.java @@ -0,0 +1,61 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DiesCreatureTriggeredAbility; +import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.effects.common.*; +import mage.abilities.keyword.TransformAbility; +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.watchers.common.AbilityResolvedWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SephirothFabledSOLDIER extends CardImpl { + + public SephirothFabledSOLDIER(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.AVATAR); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + this.secondSideCardClazz = mage.cards.s.SephirothOneWingedAngel.class; + + // Whenever Sephiroth enters or attacks, you may sacrifice another creature. If you do, draw a card. + this.addAbility(new EntersBattlefieldOrAttacksSourceTriggeredAbility(new DoIfCostPaid( + new DrawCardSourceControllerEffect(1), + new SacrificeTargetCost(StaticFilters.FILTER_ANOTHER_CREATURE) + ))); + + // Whenever another creature dies, target opponent loses 1 life and you gain 1 life. If this is the fourth time this ability has resolved this turn, transform Sephiroth. + this.addAbility(new TransformAbility()); + Ability ability = new DiesCreatureTriggeredAbility( + new LoseLifeTargetEffect(1), false, + StaticFilters.FILTER_ANOTHER_CREATURE + ); + ability.addEffect(new GainLifeEffect(1).concatBy("and")); + ability.addEffect(new IfAbilityHasResolvedXTimesEffect(4, new TransformSourceEffect())); + this.addAbility(ability, new AbilityResolvedWatcher()); + } + + private SephirothFabledSOLDIER(final SephirothFabledSOLDIER card) { + super(card); + } + + @Override + public SephirothFabledSOLDIER copy() { + return new SephirothFabledSOLDIER(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SephirothFallenHero.java b/Mage.Sets/src/mage/cards/s/SephirothFallenHero.java new file mode 100644 index 00000000000..62be3e2c439 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SephirothFallenHero.java @@ -0,0 +1,97 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ReturnSourceFromGraveyardToBattlefieldEffect; +import mage.abilities.effects.common.continuous.SetBasePowerToughnessAllEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.permanent.ModifiedPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SephirothFallenHero extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledCreaturePermanent(); + private static final FilterPermanent filter2 = new FilterPermanent("a modified creature"); + + static { + filter.add(ModifiedPredicate.instance); + filter2.add(ModifiedPredicate.instance); + } + + public SephirothFallenHero(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.AVATAR); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(7); + this.toughness = new MageInt(5); + + // Jenova Cells -- Whenever Sephiroth attacks, you may put a cell counter on target creature. Until end of turn, each modified creature you control has base power and toughness 7/5. + Ability ability = new AttacksTriggeredAbility(new SephirothFallenHeroEffect()); + ability.addEffect(new SetBasePowerToughnessAllEffect(7, 5, Duration.EndOfTurn, filter)); + ability.addTarget(new TargetCreaturePermanent()); + this.addAbility(ability.withFlavorWord("Jenova Cells")); + + // The Reunion -- {3}, Sacrifice a modified creature: Return this card from your graveyard to the battlefield tapped. + ability = new SimpleActivatedAbility( + Zone.GRAVEYARD, new ReturnSourceFromGraveyardToBattlefieldEffect(true), new GenericManaCost(3) + ); + ability.addCost(new SacrificeTargetCost(filter2)); + this.addAbility(ability.withFlavorWord("The Reunion")); + } + + private SephirothFallenHero(final SephirothFallenHero card) { + super(card); + } + + @Override + public SephirothFallenHero copy() { + return new SephirothFallenHero(this); + } +} + +class SephirothFallenHeroEffect extends OneShotEffect { + + SephirothFallenHeroEffect() { + super(Outcome.Benefit); + staticText = "you may put a cell counter on target creature"; + } + + private SephirothFallenHeroEffect(final SephirothFallenHeroEffect effect) { + super(effect); + } + + @Override + public SephirothFallenHeroEffect copy() { + return new SephirothFallenHeroEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + return player != null && permanent != null + && player.chooseUse(outcome, "Put a cell counter on " + permanent.getIdName() + "?", source, game) + && permanent.addCounters(CounterType.CELL.createInstance(), source, game); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SephirothOneWingedAngel.java b/Mage.Sets/src/mage/cards/s/SephirothOneWingedAngel.java new file mode 100644 index 00000000000..7b4cb758e14 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SephirothOneWingedAngel.java @@ -0,0 +1,136 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.ReplacementEffectImpl; +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.command.emblems.SephirothOneWingedAngelEmblem; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetSacrifice; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SephirothOneWingedAngel extends CardImpl { + + public SephirothOneWingedAngel(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.ANGEL); + this.subtype.add(SubType.NIGHTMARE); + this.subtype.add(SubType.AVATAR); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + this.nightCard = true; + this.color.setBlack(true); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Super Nova -- As this creature transforms into Sephiroth, One-Winged Angel, you get an emblem with "Whenever a creature dies, target opponent loses 1 life and you gain 1 life." + this.addAbility(new SimpleStaticAbility(new SephirothOneWingedAngelEmblemEffect()).withFlavorWord("Super Nova")); + + // Whenever Sephiroth attacks, you may sacrifice any number of other creatures. If you do, draw that many cards. + this.addAbility(new AttacksTriggeredAbility(new SephirothOneWingedAngelSacrificeEffect())); + } + + private SephirothOneWingedAngel(final SephirothOneWingedAngel card) { + super(card); + } + + @Override + public SephirothOneWingedAngel copy() { + return new SephirothOneWingedAngel(this); + } +} + +class SephirothOneWingedAngelEmblemEffect extends ReplacementEffectImpl { + + SephirothOneWingedAngelEmblemEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit); + staticText = "as this creature transforms into {this}, " + + "you get an emblem with \"Whenever a creature dies, target opponent loses 1 life and you gain 1 life.\""; + } + + private SephirothOneWingedAngelEmblemEffect(final SephirothOneWingedAngelEmblemEffect effect) { + super(effect); + } + + @Override + public SephirothOneWingedAngelEmblemEffect copy() { + return new SephirothOneWingedAngelEmblemEffect(this); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Optional.ofNullable(source.getSourceObject(game)) + .ifPresent(obj -> game.addEmblem(new SephirothOneWingedAngelEmblem(), obj, source)); + return false; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.TRANSFORMING; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + return source.getSourceId().equals(event.getTargetId()) + && source.getSourcePermanentIfItStillExists(game) != null; + } +} + +class SephirothOneWingedAngelSacrificeEffect extends OneShotEffect { + + SephirothOneWingedAngelSacrificeEffect() { + super(Outcome.Benefit); + staticText = "you may sacrifice any number of other creatures. If you do, draw that many cards"; + } + + private SephirothOneWingedAngelSacrificeEffect(final SephirothOneWingedAngelSacrificeEffect effect) { + super(effect); + } + + @Override + public SephirothOneWingedAngelSacrificeEffect copy() { + return new SephirothOneWingedAngelSacrificeEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + TargetSacrifice target = new TargetSacrifice( + 0, Integer.MAX_VALUE, StaticFilters.FILTER_ANOTHER_CREATURE + ); + player.choose(outcome, target, source, game); + int count = 0; + for (UUID targetId : target.getTargets()) { + Permanent permanent = game.getPermanent(targetId); + if (permanent != null && permanent.sacrifice(source, game)) { + count++; + } + } + if (count < 1) { + return false; + } + player.drawCards(count, source, game); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/s/SephirothsIntervention.java b/Mage.Sets/src/mage/cards/s/SephirothsIntervention.java new file mode 100644 index 00000000000..7e2396ba3bd --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SephirothsIntervention.java @@ -0,0 +1,34 @@ +package mage.cards.s; + +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.effects.common.GainLifeEffect; +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 SephirothsIntervention extends CardImpl { + + public SephirothsIntervention(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{B}"); + + // Destroy target creature. You gain 2 life. + this.getSpellAbility().addEffect(new DestroyTargetEffect()); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + this.getSpellAbility().addEffect(new GainLifeEffect(2)); + } + + private SephirothsIntervention(final SephirothsIntervention card) { + super(card); + } + + @Override + public SephirothsIntervention copy() { + return new SephirothsIntervention(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SerahFarron.java b/Mage.Sets/src/mage/cards/s/SerahFarron.java new file mode 100644 index 00000000000..5e77209c353 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SerahFarron.java @@ -0,0 +1,138 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.effects.common.cost.SpellsCostReductionControllerEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueConditionHint; +import mage.abilities.keyword.TransformAbility; +import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.WatcherScope; +import mage.filter.FilterCard; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterCreatureCard; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.stack.Spell; +import mage.watchers.Watcher; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SerahFarron extends CardImpl { + + private static final FilterCard filter + = new FilterCreatureCard("the first legendary creature spell you cast each turn"); + private static final FilterPermanent filter2 + = new FilterControlledCreaturePermanent("you control two or more other legendary creatures"); + + static { + filter.add(SuperType.LEGENDARY.getPredicate()); + filter.add(SerahFarronPredicate.instance); + filter2.add(AnotherPredicate.instance); + filter2.add(SuperType.LEGENDARY.getPredicate()); + } + + private static final Condition condition = new PermanentsOnTheBattlefieldCondition(filter2); + private static final Hint hint = new ValueConditionHint( + "You control two or more other legendary creatures", + new PermanentsOnBattlefieldCount(filter2), condition + ); + + public SerahFarron(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.CITIZEN); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + this.secondSideCardClazz = mage.cards.c.CrystallizedSerah.class; + + // The first legendary creature spell you cast each turn costs {2} less to cast. + this.addAbility(makeAbility(), new SerahFarronWatcher()); + + // At the beginning of combat on your turn, if you control two or more other legendary creatures, you may transform Serah Farron. + this.addAbility(new TransformAbility()); + this.addAbility(new BeginningOfCombatTriggeredAbility( + new TransformSourceEffect(), true + ).withInterveningIf(condition).addHint(hint)); + } + + private SerahFarron(final SerahFarron card) { + super(card); + } + + @Override + public SerahFarron copy() { + return new SerahFarron(this); + } + + public static Ability makeAbility() { + return new SimpleStaticAbility(new SpellsCostReductionControllerEffect(filter, 2)); + } + +} + +enum SerahFarronPredicate implements ObjectSourcePlayerPredicate { + instance; + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + return !SerahFarronWatcher.checkPlayer(input.getPlayerId(), game); + } + + @Override + public String toString() { + return "The first creature spell you cast each turn"; + } +} + +class SerahFarronWatcher extends Watcher { + + private final Set set = new HashSet<>(); + + public SerahFarronWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() != GameEvent.EventType.SPELL_CAST) { + return; + } + Spell spell = game.getSpell(event.getTargetId()); + if (spell != null && spell.isCreature(game) && spell.isLegendary(game)) { + set.add(event.getPlayerId()); + } + } + + @Override + public void reset() { + super.reset(); + set.clear(); + } + + static boolean checkPlayer(UUID playerId, Game game) { + return game.getState().getWatcher(SerahFarronWatcher.class).set.contains(playerId); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SetzerWanderingGambler.java b/Mage.Sets/src/mage/cards/s/SetzerWanderingGambler.java new file mode 100644 index 00000000000..e16db94ae8e --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SetzerWanderingGambler.java @@ -0,0 +1,61 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.DealsDamageToAPlayerAllTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.WonCoinFlipControllerTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.FlipCoinEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SetTargetPointer; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.game.permanent.token.TheBlackjackToken; +import mage.game.permanent.token.TreasureToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SetzerWanderingGambler extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledPermanent(SubType.VEHICLE); + + public SetzerWanderingGambler(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ROGUE); + this.subtype.add(SubType.PILOT); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // When Setzer enters, create The Blackjack, a legendary 3/3 colorless Vehicle artifact token with flying and crew 2. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new TheBlackjackToken()))); + + // Whenever a Vehicle you control deals combat damage to a player, flip a coin. + this.addAbility(new DealsDamageToAPlayerAllTriggeredAbility( + new FlipCoinEffect(), filter, false, SetTargetPointer.NONE, true + )); + + // Whenever you win a coin flip, create two tapped Treasure tokens. + this.addAbility(new WonCoinFlipControllerTriggeredAbility( + new CreateTokenEffect(new TreasureToken(), 2, true) + )); + } + + private SetzerWanderingGambler(final SetzerWanderingGambler card) { + super(card); + } + + @Override + public SetzerWanderingGambler copy() { + return new SetzerWanderingGambler(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SeymourFlux.java b/Mage.Sets/src/mage/cards/s/SeymourFlux.java new file mode 100644 index 00000000000..8cbb8382fea --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SeymourFlux.java @@ -0,0 +1,46 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.costs.common.PayLifeCost; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; +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 java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SeymourFlux extends CardImpl { + + public SeymourFlux(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPIRIT); + this.subtype.add(SubType.AVATAR); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // At the beginning of your upkeep, you may pay 1 life. If you do, draw a card and put a +1/+1 counter on Seymour Flux. + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new DoIfCostPaid( + new DrawCardSourceControllerEffect(1), new PayLifeCost(1) + ).addEffect(new AddCountersSourceEffect(CounterType.P1P1.createInstance()).concatBy("and")))); + } + + private SeymourFlux(final SeymourFlux card) { + super(card); + } + + @Override + public SeymourFlux copy() { + return new SeymourFlux(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/ShahOfNaarIsle.java b/Mage.Sets/src/mage/cards/s/ShahOfNaarIsle.java index aab3657c0b0..e4f9acd7e57 100644 --- a/Mage.Sets/src/mage/cards/s/ShahOfNaarIsle.java +++ b/Mage.Sets/src/mage/cards/s/ShahOfNaarIsle.java @@ -101,7 +101,7 @@ class ShahOfNaarIsleEffect extends OneShotEffect { for (UUID playerId : game.getOpponents(controller.getId())) { Player opponent = game.getPlayer(playerId); if (opponent != null) { - int number = opponent.getAmount(0, 3, "Draw how many cards?", game); + int number = opponent.getAmount(0, 3, "Draw how many cards?", source, game); opponent.drawCards(number, source, game); } } diff --git a/Mage.Sets/src/mage/cards/s/ShamblingCieth.java b/Mage.Sets/src/mage/cards/s/ShamblingCieth.java new file mode 100644 index 00000000000..983821038c3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/ShamblingCieth.java @@ -0,0 +1,51 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTappedAbility; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.ReturnSourceFromGraveyardToHandEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SetTargetPointer; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ShamblingCieth extends CardImpl { + + public ShamblingCieth(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); + + this.subtype.add(SubType.MUTANT); + this.subtype.add(SubType.HORROR); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // This creature enters tapped. + this.addAbility(new EntersBattlefieldTappedAbility()); + + // Whenever you cast a noncreature spell, you may pay {B}. If you do, return this card from your graveyard to your hand. + this.addAbility(new SpellCastControllerTriggeredAbility( + Zone.GRAVEYARD, + new DoIfCostPaid(new ReturnSourceFromGraveyardToHandEffect(), new ManaCostsImpl<>("{B}")), + StaticFilters.FILTER_SPELL_A_NON_CREATURE, false, SetTargetPointer.NONE + )); + } + + private ShamblingCieth(final ShamblingCieth card) { + super(card); + } + + @Override + public ShamblingCieth copy() { + return new ShamblingCieth(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/ShantottoTacticianMagician.java b/Mage.Sets/src/mage/cards/s/ShantottoTacticianMagician.java new file mode 100644 index 00000000000..1c87eaab586 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/ShantottoTacticianMagician.java @@ -0,0 +1,102 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +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.SuperType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.stack.Spell; +import mage.util.CardUtil; +import mage.watchers.common.ManaPaidSourceWatcher; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ShantottoTacticianMagician extends CardImpl { + + public ShantottoTacticianMagician(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.DWARF); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(0); + this.toughness = new MageInt(4); + + // Whenever you cast a noncreature spell, Shantotto, Tactician Magician gets +X/+0 until end of turn, where X is the amount of mana spent to cast that spell. If X is 4 or greater, draw a card. + Ability ability = new SpellCastControllerTriggeredAbility(new BoostSourceEffect( + ShantottoTacticianMagicianValue.instance, + StaticValue.get(0), Duration.EndOfTurn + ), StaticFilters.FILTER_SPELL_A_NON_CREATURE, false); + ability.addEffect(new ConditionalOneShotEffect( + new DrawCardSourceControllerEffect(1), + ShantottoTacticianMagicianCondition.instance, + "If X is 4 or greater, draw a card" + )); + this.addAbility(ability); + } + + private ShantottoTacticianMagician(final ShantottoTacticianMagician card) { + super(card); + } + + @Override + public ShantottoTacticianMagician copy() { + return new ShantottoTacticianMagician(this); + } +} + +enum ShantottoTacticianMagicianValue implements DynamicValue { + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return Optional + .ofNullable((Spell) effect.getValue("spellCast")) + .map(spell -> ManaPaidSourceWatcher.getTotalPaid(spell.getId(), game)) + .orElse(0); + } + + @Override + public ShantottoTacticianMagicianValue copy() { + return this; + } + + @Override + public String getMessage() { + return "the amount of mana spent to cast that spell"; + } + + @Override + public String toString() { + return "X"; + } +} + +enum ShantottoTacticianMagicianCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + return CardUtil + .getEffectValueFromAbility(source, "spellCast", Spell.class) + .map(spell -> ManaPaidSourceWatcher.getTotalPaid(spell.getId(), game) >= 4) + .orElse(false); + } +} diff --git a/Mage.Sets/src/mage/cards/s/ShayCormac.java b/Mage.Sets/src/mage/cards/s/ShayCormac.java new file mode 100644 index 00000000000..5878a3d67ff --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/ShayCormac.java @@ -0,0 +1,121 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.common.BecomesTargetAnyTriggeredAbility; +import mage.abilities.common.DiesCreatureTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.keyword.*; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.FilterStackObject; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreaturePermanent; +import mage.game.Game; +import mage.game.permanent.Permanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ShayCormac extends CardImpl { + + private static final FilterStackObject filter = new FilterStackObject("a spell or ability you control"); + private static final FilterPermanent filter2 = new FilterCreaturePermanent("a creature with a bounty counter on it"); + + static { + filter.add(TargetController.YOU.getControllerPredicate()); + filter2.add(CounterType.BOUNTY.getPredicate()); + } + + public ShayCormac(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.KNIGHT); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // {1}: Permanents your opponents control lose hexproof, indestructible, protection, shroud, and ward until end of turn. + this.addAbility(new SimpleActivatedAbility(new ShayCormacEffect(), new GenericManaCost(1))); + + // Whenever a creature an opponent controls becomes the target of a spell or ability you control, put a bounty counter on that creature. + this.addAbility(new BecomesTargetAnyTriggeredAbility( + new AddCountersTargetEffect(CounterType.BOUNTY.createInstance()), + StaticFilters.FILTER_OPPONENTS_PERMANENT_A_CREATURE, filter + )); + + // Whenever a creature with a bounty counter on it dies, put two +1/+1 counters on Shay Cormac. + this.addAbility(new DiesCreatureTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), false, filter2 + )); + } + + private ShayCormac(final ShayCormac card) { + super(card); + } + + @Override + public ShayCormac copy() { + return new ShayCormac(this); + } +} + +class ShayCormacEffect extends ContinuousEffectImpl { + + ShayCormacEffect() { + super(Duration.EndOfTurn, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.Benefit); + staticText = "permanents your opponents control lose hexproof, " + + "indestructible, protection, shroud, and ward until end of turn"; + } + + private ShayCormacEffect(final ShayCormacEffect effect) { + super(effect); + } + + @Override + public ShayCormacEffect copy() { + return new ShayCormacEffect(this); + } + + @Override + public void init(Ability source, Game game) { + super.init(source, game); + game.getBattlefield() + .getActivePermanents( + StaticFilters.FILTER_OPPONENTS_PERMANENT, + source.getControllerId(), source, game + ) + .stream() + .map(permanent -> new MageObjectReference(permanent, game)) + .forEach(affectedObjectList::add); + } + + @Override + public boolean apply(Game game, Ability source) { + for (MageObjectReference mor : affectedObjectList) { + Permanent permanent = mor.getPermanent(game); + if (permanent == null) { + continue; + } + // I know the removeIf is deprecated but I can't see any reason not to use it based on what the removeAbility method actually does + permanent.getAbilities(game).removeIf(HexproofBaseAbility.class::isInstance); + permanent.removeAbility(IndestructibleAbility.getInstance(), source.getSourceId(), game); + permanent.getAbilities(game).removeIf(ProtectionAbility.class::isInstance); + permanent.removeAbility(ShroudAbility.getInstance(), source.getSourceId(), game); + permanent.getAbilities(game).removeIf(WardAbility.class::isInstance); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/s/ShelindaYevonAcolyte.java b/Mage.Sets/src/mage/cards/s/ShelindaYevonAcolyte.java new file mode 100644 index 00000000000..ea2d2c82fa8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/ShelindaYevonAcolyte.java @@ -0,0 +1,76 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.keyword.LifelinkAbility; +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.permanent.Permanent; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ShelindaYevonAcolyte extends CardImpl { + + public ShelindaYevonAcolyte(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.CLERIC); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // Whenever another creature you control enters, put a +1/+1 counter on that creature if its power is less than Shelinda's power. Otherwise, put a +1/+1 counter on Shelinda. + this.addAbility(new EntersBattlefieldAllTriggeredAbility( + Zone.BATTLEFIELD, + new ConditionalOneShotEffect( + new AddCountersTargetEffect(CounterType.P1P1.createInstance()), + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), + ShelindaYevonAcolyteCondition.instance, "put a +1/+1 counter on that creature " + + "if its power is less than {this}'s power. Otherwise, put a +1/+1 counter on {this}" + ), StaticFilters.FILTER_ANOTHER_CREATURE_YOU_CONTROL, false, SetTargetPointer.PERMANENT + )); + } + + private ShelindaYevonAcolyte(final ShelindaYevonAcolyte card) { + super(card); + } + + @Override + public ShelindaYevonAcolyte copy() { + return new ShelindaYevonAcolyte(this); + } +} + +enum ShelindaYevonAcolyteCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = source.getSourcePermanentOrLKI(game); + return permanent != null + && CardUtil + .getEffectValueFromAbility(source, "permanentEnteringBattlefield", Permanent.class) + .map(MageObject::getPower) + .map(MageInt::getValue) + .map(x -> x < permanent.getPower().getValue()) + .orElse(false); + } +} diff --git a/Mage.Sets/src/mage/cards/s/ShelobChildOfUngoliant.java b/Mage.Sets/src/mage/cards/s/ShelobChildOfUngoliant.java index 51cd8a005ce..e55e73b6798 100644 --- a/Mage.Sets/src/mage/cards/s/ShelobChildOfUngoliant.java +++ b/Mage.Sets/src/mage/cards/s/ShelobChildOfUngoliant.java @@ -228,7 +228,7 @@ class ShelobChildOfUngoliantEffect extends OneShotEffect { token.addSubType(SubType.FOOD); // {2}, {T}, Sacrifice this artifact: You gain 3 life. - token.addAbility(new FoodAbility(false)); + token.addAbility(new FoodAbility()); }).apply(game, source); } } diff --git a/Mage.Sets/src/mage/cards/s/ShinraReinforcements.java b/Mage.Sets/src/mage/cards/s/ShinraReinforcements.java new file mode 100644 index 00000000000..d2da7930452 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/ShinraReinforcements.java @@ -0,0 +1,42 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.MillCardsControllerEffect; +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 ShinraReinforcements extends CardImpl { + + public ShinraReinforcements(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // When this creature enters, mill three cards and you gain 3 life. + Ability ability = new EntersBattlefieldTriggeredAbility(new MillCardsControllerEffect(3)); + ability.addEffect(new GainLifeEffect(3).concatBy("and")); + this.addAbility(ability); + } + + private ShinraReinforcements(final ShinraReinforcements card) { + super(card); + } + + @Override + public ShinraReinforcements copy() { + return new ShinraReinforcements(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/ShinryuTranscendentRival.java b/Mage.Sets/src/mage/cards/s/ShinryuTranscendentRival.java new file mode 100644 index 00000000000..3fdbaeda184 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/ShinryuTranscendentRival.java @@ -0,0 +1,134 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.WinGameSourceControllerEffect; +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.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPlayer; +import mage.target.common.TargetOpponent; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ShinryuTranscendentRival extends CardImpl { + + public ShinryuTranscendentRival(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(8); + this.toughness = new MageInt(8); + this.color.setBlack(true); + this.nightCard = true; + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // As this creature transforms into Shinryu, choose an opponent. + this.addAbility(new SimpleStaticAbility(new ShinryuTranscendentRivalEffect())); + + // Burning Chains -- When the chosen player loses the game, you win the game. + this.addAbility(new ShinryuTranscendentRivalTriggeredAbility()); + } + + private ShinryuTranscendentRival(final ShinryuTranscendentRival card) { + super(card); + } + + @Override + public ShinryuTranscendentRival copy() { + return new ShinryuTranscendentRival(this); + } +} + +class ShinryuTranscendentRivalEffect extends ReplacementEffectImpl { + + ShinryuTranscendentRivalEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit); + staticText = "as this creature transforms into {this}, choose an opponent"; + } + + private ShinryuTranscendentRivalEffect(final ShinryuTranscendentRivalEffect effect) { + super(effect); + } + + @Override + public ShinryuTranscendentRivalEffect copy() { + return new ShinryuTranscendentRivalEffect(this); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Player controller = game.getPlayer(source.getControllerId()); + Permanent permanent = game.getPermanent(source.getSourceId()); + if (controller == null || permanent == null) { + return false; + } + TargetPlayer target = new TargetOpponent(true); + controller.choose(outcome, target, source, game); + Player opponent = game.getPlayer(target.getFirstTarget()); + if (opponent == null) { + return false; + } + game.informPlayers(permanent.getName() + ": " + controller.getLogName() + " has chosen " + opponent.getLogName()); + game.getState().setValue(permanent.getId() + "_" + permanent.getZoneChangeCounter(game) + "_opponent", opponent.getId()); + return false; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.TRANSFORMING; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + return source.getSourceId().equals(event.getTargetId()) + && source.getSourcePermanentIfItStillExists(game) != null; + } +} + +class ShinryuTranscendentRivalTriggeredAbility extends TriggeredAbilityImpl { + + ShinryuTranscendentRivalTriggeredAbility() { + super(Zone.BATTLEFIELD, new WinGameSourceControllerEffect()); + this.setTriggerPhrase("When the chosen player loses the game, "); + this.withFlavorWord("Burning Chains"); + } + + private ShinryuTranscendentRivalTriggeredAbility(final ShinryuTranscendentRivalTriggeredAbility ability) { + super(ability); + } + + @Override + public ShinryuTranscendentRivalTriggeredAbility copy() { + return new ShinryuTranscendentRivalTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.LOSES; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return Optional + .ofNullable(this.getId() + "_" + this.getSourceObjectZoneChangeCounter() + "_opponent") + .map(game.getState()::getValue) + .map(event.getPlayerId()::equals) + .orElse(false); + } +} diff --git a/Mage.Sets/src/mage/cards/s/ShivaWardenOfIce.java b/Mage.Sets/src/mage/cards/s/ShivaWardenOfIce.java new file mode 100644 index 00000000000..44eb95bf35b --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/ShivaWardenOfIce.java @@ -0,0 +1,66 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.SagaAbility; +import mage.abilities.effects.common.ExileAndReturnSourceEffect; +import mage.abilities.effects.common.TapAllEffect; +import mage.abilities.effects.common.combat.CantBeBlockedTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterLandPermanent; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ShivaWardenOfIce extends CardImpl { + + private static final FilterPermanent filter = new FilterLandPermanent("lands your opponents control"); + + static { + filter.add(TargetController.OPPONENT.getControllerPredicate()); + } + + public ShivaWardenOfIce(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, ""); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SAGA); + this.subtype.add(SubType.ELEMENTAL); + this.power = new MageInt(4); + this.toughness = new MageInt(5); + this.nightCard = true; + this.color.setBlue(true); + + // (As this Saga enters and after your draw step, add a lore counter.) + SagaAbility sagaAbility = new SagaAbility(this); + + // I, II -- Mesmerize -- Target creature can't be blocked this turn. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_I, SagaChapter.CHAPTER_II, ability -> { + ability.addEffect(new CantBeBlockedTargetEffect(Duration.EndOfTurn)); + ability.addTarget(new TargetCreaturePermanent()); + ability.withFlavorWord("Mesmerize"); + }); + + // III -- Cold Snap -- Tap all lands your opponents control. Exile Shiva, then return it to the battlefield. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_III, ability -> { + ability.addEffect(new TapAllEffect(filter)); + ability.addEffect(new ExileAndReturnSourceEffect(PutCards.BATTLEFIELD)); + ability.withFlavorWord("Cold Snap"); + }); + this.addAbility(sagaAbility); + } + + private ShivaWardenOfIce(final ShivaWardenOfIce card) { + super(card); + } + + @Override + public ShivaWardenOfIce copy() { + return new ShivaWardenOfIce(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SibyllineSoothsayer.java b/Mage.Sets/src/mage/cards/s/SibyllineSoothsayer.java index 64b31875f49..cad2c8ada40 100644 --- a/Mage.Sets/src/mage/cards/s/SibyllineSoothsayer.java +++ b/Mage.Sets/src/mage/cards/s/SibyllineSoothsayer.java @@ -1,20 +1,16 @@ package mage.cards.s; import mage.MageInt; -import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.continuous.GainSuspendEffect; import mage.abilities.keyword.SuspendAbility; import mage.cards.*; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.Zone; -import mage.counters.CounterType; import mage.game.Game; -import mage.players.Library; import mage.players.Player; import java.util.UUID; @@ -49,8 +45,10 @@ class SibyllineSoothsayerEffect extends OneShotEffect { SibyllineSoothsayerEffect() { super(Outcome.Benefit); - staticText = "reveal cards from the top of your library until you reveal a nonland card with mana value 3 or greater. Exile that card with three time " + - "counters on it. If it doesn't have suspend, it gains suspend. Put the rest of the revealed cards on the bottom of your library in a random order."; + staticText = "reveal cards from the top of your library until you reveal a nonland card " + + "with mana value 3 or greater. Exile that card with three time counters on it. " + + "If it doesn't have suspend, it gains suspend. Put the rest of the revealed cards " + + "on the bottom of your library in a random order."; } private SibyllineSoothsayerEffect(final SibyllineSoothsayerEffect effect) { @@ -65,36 +63,20 @@ class SibyllineSoothsayerEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); - if (player == null) { + if (player == null || !player.getLibrary().hasCards()) { return false; } - Library library = player.getLibrary(); - if (!library.hasCards()) { - return true; - } Cards cards = new CardsImpl(); - Card toSuspend = null; - for (Card card : library.getCards(game)) { + for (Card card : player.getLibrary().getCards(game)) { cards.add(card); if (!card.isLand(game) && card.getManaValue() >= 3) { - toSuspend = card; + player.revealCards(source, cards, game); + player.moveCards(card, Zone.EXILED, source, game); + SuspendAbility.addTimeCountersAndSuspend(card, 3, source, game); break; } } - - player.revealCards(source, cards, game); - if (toSuspend != null) { - boolean hasSuspend = toSuspend.getAbilities(game).containsClass(SuspendAbility.class); - UUID exileId = SuspendAbility.getSuspendExileId(player.getId(), game); - if (player.moveCardToExileWithInfo(toSuspend, exileId, "Suspended cards of " + player.getName(), source, game, Zone.LIBRARY, true)) { - toSuspend.addCounters(CounterType.TIME.createInstance(3), source.getControllerId(), source, game); - if (!hasSuspend) { - game.addEffect(new GainSuspendEffect(new MageObjectReference(toSuspend, game)), source); - } - game.informPlayers(player.getLogName() + " suspends 3 - " + toSuspend.getName()); - } - } - cards.remove(toSuspend); + cards.retainZone(Zone.LIBRARY, game); if (!cards.isEmpty()) { player.putCardsOnBottomOfLibrary(cards, game, source, false); } diff --git a/Mage.Sets/src/mage/cards/s/SidisiRegentOfTheMire.java b/Mage.Sets/src/mage/cards/s/SidisiRegentOfTheMire.java index ac7656de699..9bf7c2f7a9d 100644 --- a/Mage.Sets/src/mage/cards/s/SidisiRegentOfTheMire.java +++ b/Mage.Sets/src/mage/cards/s/SidisiRegentOfTheMire.java @@ -75,7 +75,7 @@ class SidisiRegentOfTheMireCost extends VariableCostImpl { public SidisiRegentOfTheMireCost() { super(VariableCostType.NORMAL, "mana value X"); - this.text = "Sacrifice a creature with mana value X"; + this.text = "Sacrifice a creature you control with mana value X other than {this}"; } protected SidisiRegentOfTheMireCost(final SidisiRegentOfTheMireCost cost) { @@ -103,4 +103,4 @@ class SidisiRegentOfTheMireCost extends VariableCostImpl { ).stream().mapToInt(MageObject::getManaValue).max().orElse(0); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/s/SiegfriedFamedSwordsman.java b/Mage.Sets/src/mage/cards/s/SiegfriedFamedSwordsman.java new file mode 100644 index 00000000000..2227fefe4d3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SiegfriedFamedSwordsman.java @@ -0,0 +1,62 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; +import mage.abilities.effects.common.MillCardsControllerEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SiegfriedFamedSwordsman extends CardImpl { + + private static final DynamicValue xValue = new CardsInControllerGraveyardCount(StaticFilters.FILTER_CARD_CREATURE, 2); + private static final Hint hint = new ValueHint( + "Creatures in your graveyard", new CardsInControllerGraveyardCount(StaticFilters.FILTER_CARD_CREATURE) + ); + + public SiegfriedFamedSwordsman(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Menace + this.addAbility(new MenaceAbility()); + + // When Siegfried enters, mill three cards. Then put X +1/+1 counters on Siegfried, where X is twice the number of creature cards in your graveyard. + Ability ability = new EntersBattlefieldTriggeredAbility(new MillCardsControllerEffect(3)); + ability.addEffect(new AddCountersSourceEffect(CounterType.P1P1.createInstance(), xValue) + .setText("Then put X +1/+1 counters on {this}, where X is " + + "twice the number of creature cards in your graveyard")); + this.addAbility(ability.addHint(hint)); + } + + private SiegfriedFamedSwordsman(final SiegfriedFamedSwordsman card) { + super(card); + } + + @Override + public SiegfriedFamedSwordsman copy() { + return new SiegfriedFamedSwordsman(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SigurdJarlOfRavensthorpe.java b/Mage.Sets/src/mage/cards/s/SigurdJarlOfRavensthorpe.java index 8e8800bbf03..d601a1f99ea 100644 --- a/Mage.Sets/src/mage/cards/s/SigurdJarlOfRavensthorpe.java +++ b/Mage.Sets/src/mage/cards/s/SigurdJarlOfRavensthorpe.java @@ -128,10 +128,13 @@ class SigurdJarlOfRavensthorpeTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { Permanent permanent = game.getPermanent(event.getTargetId()); + if (permanent == null) { + permanent = game.getPermanentEntering(event.getTargetId()); + } return permanent != null && permanent.hasSubtype(SubType.SAGA, game) && permanent.isControlledBy(getControllerId()) - && CounterType.P1P1.getName().equals(event.getData()) + && CounterType.LORE.getName().equals(event.getData()) && isControlledBy(event.getPlayerId()); } } diff --git a/Mage.Sets/src/mage/cards/s/SilkweaverElite.java b/Mage.Sets/src/mage/cards/s/SilkweaverElite.java index d860ded15c7..41117ad2d49 100644 --- a/Mage.Sets/src/mage/cards/s/SilkweaverElite.java +++ b/Mage.Sets/src/mage/cards/s/SilkweaverElite.java @@ -3,7 +3,6 @@ package mage.cards.s; import mage.MageInt; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.condition.common.RevoltCondition; -import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.keyword.ReachAbility; import mage.cards.CardImpl; @@ -32,11 +31,10 @@ public final class SilkweaverElite extends CardImpl { this.addAbility(ReachAbility.getInstance()); // Revolt — When Silkweaver Elite enters the battlefield, if a permanent you controlled left the battlefield this turn, draw a card. - this.addAbility(new ConditionalInterveningIfTriggeredAbility( - new EntersBattlefieldTriggeredAbility(new DrawCardSourceControllerEffect(1), false), - RevoltCondition.instance, "When {this} enters, " + - "if a permanent you controlled left the battlefield this turn, draw a card." - ).setAbilityWord(AbilityWord.REVOLT).addHint(RevoltCondition.getHint()), new RevoltWatcher()); + this.addAbility(new EntersBattlefieldTriggeredAbility(new DrawCardSourceControllerEffect(1)) + .withInterveningIf(RevoltCondition.instance) + .setAbilityWord(AbilityWord.REVOLT) + .addHint(RevoltCondition.getHint()), new RevoltWatcher()); } private SilkweaverElite(final SilkweaverElite card) { diff --git a/Mage.Sets/src/mage/cards/s/SimicCharm.java b/Mage.Sets/src/mage/cards/s/SimicCharm.java index a9eca52671b..1d973643d0b 100644 --- a/Mage.Sets/src/mage/cards/s/SimicCharm.java +++ b/Mage.Sets/src/mage/cards/s/SimicCharm.java @@ -1,40 +1,38 @@ - package mage.cards.s; -import java.util.UUID; import mage.abilities.Mode; import mage.abilities.effects.common.ReturnToHandTargetEffect; import mage.abilities.effects.common.continuous.BoostTargetEffect; -import mage.abilities.effects.common.continuous.GainAbilityAllEffect; import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; import mage.abilities.keyword.HexproofAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; -import mage.filter.common.FilterControlledPermanent; +import mage.filter.StaticFilters; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + /** - * * @author Plopman */ public final class SimicCharm extends CardImpl { - public SimicCharm (UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{G}{U}"); + public SimicCharm(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{G}{U}"); //Choose one - Target creature gets +3/+3 until end of turn this.getSpellAbility().addEffect(new BoostTargetEffect(3, 3, Duration.EndOfTurn)); this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + //permanents you control gain hexproof until end of turn - Mode mode = new Mode(new GainAbilityControlledEffect(HexproofAbility.getInstance(), Duration.EndOfTurn)); - this.getSpellAbility().addMode(mode); + this.getSpellAbility().addMode(new Mode(new GainAbilityControlledEffect(HexproofAbility.getInstance(), Duration.EndOfTurn, StaticFilters.FILTER_PERMANENTS))); + //return target creature to its owner's hand. Mode mode2 = new Mode(new ReturnToHandTargetEffect()); mode2.addTarget(new TargetCreaturePermanent()); this.getSpellAbility().addMode(mode2); - } private SimicCharm(final SimicCharm card) { @@ -42,7 +40,7 @@ public final class SimicCharm extends CardImpl { } @Override - public SimicCharm copy() { + public SimicCharm copy() { return new SimicCharm(this); } } diff --git a/Mage.Sets/src/mage/cards/s/SinisterConcierge.java b/Mage.Sets/src/mage/cards/s/SinisterConcierge.java index 294258a3ec4..f7ae1aced8b 100644 --- a/Mage.Sets/src/mage/cards/s/SinisterConcierge.java +++ b/Mage.Sets/src/mage/cards/s/SinisterConcierge.java @@ -1,19 +1,9 @@ package mage.cards.s; import mage.MageInt; -import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.common.DiesSourceTriggeredAbility; -import mage.abilities.costs.Cost; -import mage.abilities.costs.common.ExileSourceWithTimeCountersCost; -import mage.abilities.dynamicvalue.common.StaticValue; -import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.DoIfCostPaid; -import mage.abilities.effects.common.ExileTargetEffect; -import mage.abilities.effects.common.continuous.GainSuspendEffect; -import mage.abilities.effects.common.counter.AddCountersSourceEffect; -import mage.abilities.effects.common.counter.AddCountersTargetEffect; import mage.abilities.keyword.SuspendAbility; import mage.cards.Card; import mage.cards.CardImpl; @@ -22,7 +12,6 @@ import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.Zone; -import mage.counters.CounterType; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; @@ -47,15 +36,7 @@ public class SinisterConcierge extends CardImpl { // Each card exiled this way that doesn't have suspend gains suspend. // (For each card with suspend, its owner removes a time counter from it at the beginning of their upkeep. // When the last is removed, they may cast it without paying its mana cost. Those creature spells have haste.) - Cost cost = new ExileSourceWithTimeCountersCost(3, false, true, Zone.GRAVEYARD); - // Paying the cost sends the Concierge to the right exile zone (Suspended cards of…) and gives it suspend. - cost.setText("exile it and put three time counters on it"); - Ability ability = new DiesSourceTriggeredAbility( - new DoIfCostPaid( - new SinisterConciergeEffect(), - cost - ) - ); + Ability ability = new DiesSourceTriggeredAbility(new SinisterConciergeEffect(), true); ability.addTarget(new TargetCreaturePermanent(0, 1)); this.addAbility(ability); } @@ -73,8 +54,8 @@ public class SinisterConcierge extends CardImpl { class SinisterConciergeEffect extends OneShotEffect { public SinisterConciergeEffect() { super(Outcome.Removal); - this.staticText = "exile up to one target creature and put three time counters on it. " + - "Each card exiled this way that doesn't have suspend gains suspend. " + + this.staticText = "exile it and put three time counters on it. If you do, exile up to one target creature " + + "and put three time counters on it. Each card exiled this way that doesn't have suspend gains suspend. " + "(For each card with suspend, its owner removes a time counter from it at the beginning of their upkeep. " + "When the last is removed, they may cast it without paying its mana cost. Those creature spells have haste.)"; } @@ -87,33 +68,18 @@ class SinisterConciergeEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); Card card = game.getCard(source.getSourceId()); - if (controller == null || card == null) { + if (controller == null || card == null + || card.getZoneChangeCounter(game) != source.getSourceObjectZoneChangeCounter() + || !Zone.GRAVEYARD.match(game.getState().getZone(card.getId()))) { return false; } - + controller.moveCards(card, Zone.EXILED, source, game); + SuspendAbility.addTimeCountersAndSuspend(card, 3, source, game); Permanent targetCreature = game.getPermanent(this.getTargetPointer().getFirst(game, source)); - if (targetCreature == null){ - return false; + if (targetCreature != null) { + controller.moveCards(targetCreature, Zone.EXILED, source, game); + SuspendAbility.addTimeCountersAndSuspend(targetCreature.getMainCard(), 3, source, game); } - - // Exile, put time counters, and give suspend for target - Player controllerTarget = game.getPlayer(targetCreature.getControllerId()); - UUID exileId = SuspendAbility.getSuspendExileId(controllerTarget.getId(), game); - Effect exileTarget = new ExileTargetEffect(exileId, "Suspended cards of " + controllerTarget.getName()); - exileTarget.setTargetPointer(this.getTargetPointer().copy()); - if (exileTarget.apply(game, source)) { - Effect addCountersTargetEffect = new AddCountersTargetEffect(CounterType.TIME.createInstance(3)); - addCountersTargetEffect.setTargetPointer(this.getTargetPointer().copy()); - boolean targetCardShouldGetSuspend = addCountersTargetEffect.apply(game, source); - - if (targetCardShouldGetSuspend && !targetCreature.getAbilities(game).containsClass(SuspendAbility.class)) { - Card targetCard = game.getCard(getTargetPointer().getFirst(game, source)); - if (!targetCard.getAbilities(game).containsClass(SuspendAbility.class)) { - game.addEffect(new GainSuspendEffect(new MageObjectReference(targetCard, game)), source); - } - } - } - return true; } diff --git a/Mage.Sets/src/mage/cards/s/SixyBeast.java b/Mage.Sets/src/mage/cards/s/SixyBeast.java index 8c5bc15a5af..5026543df75 100644 --- a/Mage.Sets/src/mage/cards/s/SixyBeast.java +++ b/Mage.Sets/src/mage/cards/s/SixyBeast.java @@ -67,7 +67,7 @@ class SixyBeastEffect extends OneShotEffect { Permanent permanent = game.getPermanentEntering(source.getSourceId()); Player controller = game.getPlayer(source.getControllerId()); if (permanent != null && controller != null) { - int counterAmount = controller.getAmount(0, 6, "Secretly put up to six counters on " + permanent.getName(), game); + int counterAmount = controller.getAmount(0, 6, "Secretly put up to six counters on " + permanent.getName(), source, game); permanent.addCounters(CounterType.P1P1.createInstance(counterAmount), source.getControllerId(), source, game); Player opponent = null; Set opponents = game.getOpponents(source.getControllerId()); @@ -82,7 +82,7 @@ class SixyBeastEffect extends OneShotEffect { } } if (opponent != null) { - int guessedAmount = opponent.getAmount(0, 6, "Guess the number of counters on " + permanent.getName(), game); + int guessedAmount = opponent.getAmount(0, 6, "Guess the number of counters on " + permanent.getName(), source, game); game.informPlayers(opponent.getLogName() + " guessed " + guessedAmount + " as the number of counters on " + permanent.getLogName()); if (counterAmount == guessedAmount) { permanent.sacrifice(source, game); diff --git a/Mage.Sets/src/mage/cards/s/SkyBlessedSamurai.java b/Mage.Sets/src/mage/cards/s/SkyBlessedSamurai.java index 370051a9daa..fc5ed5454ba 100644 --- a/Mage.Sets/src/mage/cards/s/SkyBlessedSamurai.java +++ b/Mage.Sets/src/mage/cards/s/SkyBlessedSamurai.java @@ -2,9 +2,8 @@ package mage.cards.s; import mage.MageInt; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; -import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; +import mage.abilities.effects.common.AffinityEffect; import mage.abilities.hint.Hint; import mage.abilities.hint.ValueHint; import mage.abilities.keyword.FlyingAbility; @@ -13,7 +12,8 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledEnchantmentPermanent; +import mage.filter.common.FilterControlledPermanent; import java.util.UUID; @@ -22,10 +22,8 @@ import java.util.UUID; */ public final class SkyBlessedSamurai extends CardImpl { - private static final DynamicValue xValue = new PermanentsOnBattlefieldCount( - StaticFilters.FILTER_CONTROLLED_PERMANENT_ENCHANTMENT - ); - private static final Hint hint = new ValueHint("Enchantments you control", xValue); + static final FilterControlledPermanent filter = new FilterControlledEnchantmentPermanent("enchantments"); + private static final Hint hint = new ValueHint("Enchantments you control", new PermanentsOnBattlefieldCount(filter)); public SkyBlessedSamurai(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, "{6}{W}"); @@ -36,9 +34,7 @@ public final class SkyBlessedSamurai extends CardImpl { this.toughness = new MageInt(4); // This spell costs {1} less to cast for each enchantment you control. - this.addAbility(new SimpleStaticAbility(Zone.ALL, - new SpellCostReductionForEachSourceEffect(1, xValue) - ).addHint(hint)); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new AffinityEffect(filter)).addHint(hint)); // Flying this.addAbility(FlyingAbility.getInstance()); diff --git a/Mage.Sets/src/mage/cards/s/SlipperyBogbonder.java b/Mage.Sets/src/mage/cards/s/SlipperyBogbonder.java index 09807275293..0dc8d5760ed 100644 --- a/Mage.Sets/src/mage/cards/s/SlipperyBogbonder.java +++ b/Mage.Sets/src/mage/cards/s/SlipperyBogbonder.java @@ -120,7 +120,7 @@ class SlipperyBogbonderEffect extends OneShotEffect { int num = player.getAmount( 0, entry.getValue().getCount(), "Choose how many " + entry.getKey() - + " counters to remove from " + permanent.getLogName(), game + + " counters to remove from " + permanent.getLogName(), source, game ); counterMap.computeIfAbsent( permanent.getId(), x -> new HashMap<>() diff --git a/Mage.Sets/src/mage/cards/s/SoaringLightbringer.java b/Mage.Sets/src/mage/cards/s/SoaringLightbringer.java new file mode 100644 index 00000000000..ffde788564e --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SoaringLightbringer.java @@ -0,0 +1,117 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.token.GlimmerToken; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SoaringLightbringer extends CardImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent("enchantment creatures"); + + static { + filter.add(CardType.ENCHANTMENT.getPredicate()); + } + + public SoaringLightbringer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, "{4}{W}"); + + this.subtype.add(SubType.BIRD); + this.subtype.add(SubType.GLIMMER); + this.power = new MageInt(4); + this.toughness = new MageInt(5); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Other enchantment creatures you control have flying. + this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect( + FlyingAbility.getInstance(), Duration.WhileOnBattlefield, filter, true + ))); + + // Whenever you attack a player, create a 1/1 white Glimmer enchantment creature token that's tapped and attacking that player. + this.addAbility(new SoaringLightbringerTriggeredAbility()); + } + + private SoaringLightbringer(final SoaringLightbringer card) { + super(card); + } + + @Override + public SoaringLightbringer copy() { + return new SoaringLightbringer(this); + } +} + +class SoaringLightbringerTriggeredAbility extends TriggeredAbilityImpl { + + SoaringLightbringerTriggeredAbility() { + super(Zone.BATTLEFIELD, new SoaringLightbringerEffect()); + setTriggerPhrase("Whenever you attack a player, "); + } + + private SoaringLightbringerTriggeredAbility(final SoaringLightbringerTriggeredAbility ability) { + super(ability); + } + + @Override + public SoaringLightbringerTriggeredAbility copy() { + return new SoaringLightbringerTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DEFENDER_ATTACKED; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (!isControlledBy(event.getPlayerId()) || game.getPlayer(event.getTargetId()) == null) { + return false; + } + this.getEffects().setTargetPointer(new FixedTarget(event.getTargetId())); + return true; + } +} + +class SoaringLightbringerEffect extends OneShotEffect { + + SoaringLightbringerEffect() { + super(Outcome.Benefit); + staticText = "create a 1/1 white Glimmer enchantment creature token that's tapped and attacking that player"; + } + + private SoaringLightbringerEffect(final SoaringLightbringerEffect effect) { + super(effect); + } + + @Override + public SoaringLightbringerEffect copy() { + return new SoaringLightbringerEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return new GlimmerToken().putOntoBattlefield( + 1, game, source, source.getControllerId(), true, true, + this.getTargetPointer().getFirst(game, source) + ); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SolemnRecruit.java b/Mage.Sets/src/mage/cards/s/SolemnRecruit.java index 430568a5d77..891bdfa7d76 100644 --- a/Mage.Sets/src/mage/cards/s/SolemnRecruit.java +++ b/Mage.Sets/src/mage/cards/s/SolemnRecruit.java @@ -1,22 +1,22 @@ package mage.cards.s; -import java.util.UUID; import mage.MageInt; -import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; import mage.abilities.condition.common.RevoltCondition; -import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.keyword.DoubleStrikeAbility; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.constants.AbilityWord; import mage.constants.CardType; import mage.constants.SubType; import mage.counters.CounterType; import mage.watchers.common.RevoltWatcher; +import java.util.UUID; + /** - * * @author fireshoes */ public final class SolemnRecruit extends CardImpl { @@ -33,13 +33,10 @@ public final class SolemnRecruit extends CardImpl { this.addAbility(DoubleStrikeAbility.getInstance()); // Revolt — At the beginning of your end step, if a permanent you controlled left the battlefield this turn, put a +1/+1 counter on Solemn Recruit. - this.addAbility(new ConditionalInterveningIfTriggeredAbility( - new BeginningOfEndStepTriggeredAbility( - new AddCountersSourceEffect(CounterType.P1P1.createInstance()) - ), - RevoltCondition.instance, - "Revolt — At the beginning of your end step, if a permanent you controlled left the battlefield this turn, put a +1/+1 counter on {this}." - ).addHint(RevoltCondition.getHint()), new RevoltWatcher()); + this.addAbility(new BeginningOfEndStepTriggeredAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance())) + .withInterveningIf(RevoltCondition.instance) + .setAbilityWord(AbilityWord.REVOLT) + .addHint(RevoltCondition.getHint()), new RevoltWatcher()); } private SolemnRecruit(final SolemnRecruit card) { diff --git a/Mage.Sets/src/mage/cards/s/SongcrafterMage.java b/Mage.Sets/src/mage/cards/s/SongcrafterMage.java index 900b6a01d16..a20c7f2eb9b 100644 --- a/Mage.Sets/src/mage/cards/s/SongcrafterMage.java +++ b/Mage.Sets/src/mage/cards/s/SongcrafterMage.java @@ -53,7 +53,7 @@ class SongcrafterMageEffect extends ContinuousEffectImpl { SongcrafterMageEffect() { super(Duration.EndOfTurn, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); this.staticText = "target instant or sorcery card in your graveyard gains harmonize until end of turn. " + - "The harmonize cost is equal to its mana cost"; + "Its harmonize cost is equal to its mana cost"; } private SongcrafterMageEffect(final SongcrafterMageEffect effect) { diff --git a/Mage.Sets/src/mage/cards/s/SorrowsPath.java b/Mage.Sets/src/mage/cards/s/SorrowsPath.java index 19836342a1f..6bdf665be60 100644 --- a/Mage.Sets/src/mage/cards/s/SorrowsPath.java +++ b/Mage.Sets/src/mage/cards/s/SorrowsPath.java @@ -21,7 +21,7 @@ import mage.game.events.BlockerDeclaredEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.players.Player; -import mage.target.common.TargetCreaturePermanentSameController; +import mage.target.common.TargetPermanentSameController; import java.util.HashSet; import java.util.List; @@ -44,7 +44,7 @@ public final class SorrowsPath extends CardImpl { // {T}: Choose two target blocking creatures an opponent controls. If each of those creatures could block all creatures that the other is blocking, remove both of them from combat. Each one then blocks all creatures the other was blocking. Ability ability = new SimpleActivatedAbility(new SorrowsPathSwitchBlockersEffect(), new TapSourceCost()); - ability.addTarget(new TargetCreaturePermanentSameController(2, filter)); + ability.addTarget(new TargetPermanentSameController(filter)); this.addAbility(ability); // Whenever Sorrow's Path becomes tapped, it deals 2 damage to you and each creature you control. diff --git a/Mage.Sets/src/mage/cards/s/SoulOfNewPhyrexia.java b/Mage.Sets/src/mage/cards/s/SoulOfNewPhyrexia.java index d6a0310f35a..7a33672abf0 100644 --- a/Mage.Sets/src/mage/cards/s/SoulOfNewPhyrexia.java +++ b/Mage.Sets/src/mage/cards/s/SoulOfNewPhyrexia.java @@ -1,7 +1,6 @@ package mage.cards.s; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; @@ -13,30 +12,34 @@ import mage.abilities.keyword.TrampleAbility; 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.filter.StaticFilters; + +import java.util.UUID; /** - * * @author LevelX2 */ public final class SoulOfNewPhyrexia extends CardImpl { public SoulOfNewPhyrexia(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT,CardType.CREATURE},"{6}"); + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{6}"); this.subtype.add(SubType.PHYREXIAN); this.subtype.add(SubType.AVATAR); this.power = new MageInt(6); this.toughness = new MageInt(6); - + // Trample this.addAbility(TrampleAbility.getInstance()); + // {5}: Permanents you control gain indestructible until end of turn. - this.addAbility(new SimpleActivatedAbility(new GainAbilityControlledEffect(IndestructibleAbility.getInstance(), Duration.EndOfTurn), new GenericManaCost(5))); + this.addAbility(new SimpleActivatedAbility(new GainAbilityControlledEffect(IndestructibleAbility.getInstance(), Duration.EndOfTurn, StaticFilters.FILTER_PERMANENTS), new GenericManaCost(5))); + // {5}, Exile Soul of New Phyrexia from your graveyard: Permanents you control gain indestructible until end of turn. - Ability ability = new SimpleActivatedAbility(Zone.GRAVEYARD, new GainAbilityControlledEffect(IndestructibleAbility.getInstance(), Duration.EndOfTurn), new GenericManaCost(5)); + Ability ability = new SimpleActivatedAbility(Zone.GRAVEYARD, new GainAbilityControlledEffect(IndestructibleAbility.getInstance(), Duration.EndOfTurn, StaticFilters.FILTER_PERMANENTS), new GenericManaCost(5)); ability.addCost(new ExileSourceFromGraveCost()); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/s/SphereGrid.java b/Mage.Sets/src/mage/cards/s/SphereGrid.java new file mode 100644 index 00000000000..11c76b7f273 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SphereGrid.java @@ -0,0 +1,57 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.common.DealsDamageToAPlayerAllTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.GainAbilityAllEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.keyword.ReachAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SetTargetPointer; +import mage.counters.CounterType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SphereGrid extends CardImpl { + + public SphereGrid(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{G}"); + + // Whenever a creature you control deals combat damage to a player, put a +1/+1 counter on that creature. + this.addAbility(new DealsDamageToAPlayerAllTriggeredAbility( + new AddCountersTargetEffect(CounterType.P1P1.createInstance()) + .setText("put a +1/+1 counter on that creature"), + StaticFilters.FILTER_CONTROLLED_CREATURE, false, + SetTargetPointer.PERMANENT, true + )); + + // Unlock Ability -- Creatures you control with +1/+1 counters on them have reach and trample. + Ability ability = new SimpleStaticAbility(new GainAbilityAllEffect( + ReachAbility.getInstance(), Duration.WhileOnBattlefield, + StaticFilters.FILTER_CONTROLLED_CREATURE_P1P1 + ).setText("creatures you control with +1/+1 counters on them have reach")); + ability.addEffect(new GainAbilityControlledEffect( + TrampleAbility.getInstance(), Duration.WhileOnBattlefield, + StaticFilters.FILTER_CONTROLLED_CREATURE_P1P1 + ).setText("and trample")); + this.addAbility(ability.withFlavorWord("Unlock Ability")); + } + + private SphereGrid(final SphereGrid card) { + super(card); + } + + @Override + public SphereGrid copy() { + return new SphereGrid(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SpymastersVault.java b/Mage.Sets/src/mage/cards/s/SpymastersVault.java index f2c4c70eb09..8286c996ccb 100644 --- a/Mage.Sets/src/mage/cards/s/SpymastersVault.java +++ b/Mage.Sets/src/mage/cards/s/SpymastersVault.java @@ -9,6 +9,7 @@ import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.dynamicvalue.common.CreaturesDiedThisTurnCount; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.keyword.ConniveSourceEffect; +import mage.abilities.hint.common.CreaturesDiedThisTurnHint; import mage.abilities.mana.BlackManaAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -44,7 +45,7 @@ public final class SpymastersVault extends CardImpl { Ability ability = new SimpleActivatedAbility(new SpymastersVaultEffect(), new ManaCostsImpl<>("{B}")); ability.addCost(new TapSourceCost()); ability.addTarget(new TargetControlledCreaturePermanent()); - this.addAbility(ability.addHint(CreaturesDiedThisTurnCount.getHint())); + this.addAbility(ability.addHint(CreaturesDiedThisTurnHint.instance)); } diff --git a/Mage.Sets/src/mage/cards/s/SquallSeeDMercenary.java b/Mage.Sets/src/mage/cards/s/SquallSeeDMercenary.java new file mode 100644 index 00000000000..74e645dbfca --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SquallSeeDMercenary.java @@ -0,0 +1,63 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksAloneControlledTriggeredAbility; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.DoubleStrikeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterCard; +import mage.filter.common.FilterPermanentCard; +import mage.filter.predicate.mageobject.ManaValuePredicate; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SquallSeeDMercenary extends CardImpl { + + private static final FilterCard filter = new FilterPermanentCard("permanent card with mana value 3 or less from your graveyard"); + + static { + filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, 4)); + } + + public SquallSeeDMercenary(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.KNIGHT); + this.subtype.add(SubType.MERCENARY); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // Rough Divide -- Whenever a creature you control attacks alone, it gains double strike until end of turn. + this.addAbility(new AttacksAloneControlledTriggeredAbility( + new GainAbilityTargetEffect(DoubleStrikeAbility.getInstance()), true, false + ).withFlavorWord("Rough Divide")); + + // Whenever Squall deals combat damage to a player, return target permanent card with mana value 3 or less from your graveyard to the battlefield. + Ability ability = new DealsCombatDamageToAPlayerTriggeredAbility(new ReturnFromGraveyardToBattlefieldTargetEffect()); + ability.addTarget(new TargetCardInYourGraveyard(filter)); + this.addAbility(ability); + } + + private SquallSeeDMercenary(final SquallSeeDMercenary card) { + super(card); + } + + @Override + public SquallSeeDMercenary copy() { + return new SquallSeeDMercenary(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SquealingDevil.java b/Mage.Sets/src/mage/cards/s/SquealingDevil.java index 3071f0108ee..556bc576a33 100644 --- a/Mage.Sets/src/mage/cards/s/SquealingDevil.java +++ b/Mage.Sets/src/mage/cards/s/SquealingDevil.java @@ -18,7 +18,6 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.abilities.effects.common.continuous.BoostTargetEffect; -import mage.constants.ColoredManaSymbol; import mage.constants.Duration; import mage.constants.Outcome; import mage.game.Game; @@ -80,7 +79,7 @@ class SquealingDevilEffect extends OneShotEffect { ManaCosts cost = new ManaCostsImpl<>("{X}"); if (player != null) { if (player.chooseUse(Outcome.BoostCreature, "Pay " + cost.getText() + "?", source, game)) { - int costX = player.announceXMana(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source); + int costX = player.announceX(0, Integer.MAX_VALUE, "Announce the value for {X} (pay to boost)", game, source, true); cost.add(new GenericManaCost(costX)); if (cost.pay(source, game, source, source.getControllerId(), false, null)) { Permanent permanent = game.getPermanent(source.getFirstTarget()); diff --git a/Mage.Sets/src/mage/cards/s/SqueesRevenge.java b/Mage.Sets/src/mage/cards/s/SqueesRevenge.java index abcc430c0ac..16f731a58a3 100644 --- a/Mage.Sets/src/mage/cards/s/SqueesRevenge.java +++ b/Mage.Sets/src/mage/cards/s/SqueesRevenge.java @@ -53,7 +53,7 @@ class SqueesRevengeEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); if(player != null) { - int number = player.announceXMana(0, Integer.MAX_VALUE, "Choose how many times to flip a coin", game, source); + int number = player.getAmount(0, Integer.MAX_VALUE, "Choose how many times to flip a coin", source, game); game.informPlayers(player.getLogName() + " chooses " + number + '.'); for(int i = 0; i < number; i++) { if(!player.flipCoin(source, game, true)) { diff --git a/Mage.Sets/src/mage/cards/s/StadiumHeadliner.java b/Mage.Sets/src/mage/cards/s/StadiumHeadliner.java index beb0372451b..9cf0ad36514 100644 --- a/Mage.Sets/src/mage/cards/s/StadiumHeadliner.java +++ b/Mage.Sets/src/mage/cards/s/StadiumHeadliner.java @@ -5,7 +5,9 @@ import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.SacrificeSourceCost; import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.common.CreaturesYouControlCount; import mage.abilities.dynamicvalue.common.SourcePermanentPowerValue; +import mage.abilities.effects.Effect; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.keyword.MobilizeAbility; import mage.cards.CardImpl; @@ -33,9 +35,9 @@ public final class StadiumHeadliner extends CardImpl { this.addAbility(new MobilizeAbility(1)); // {1}{R}, Sacrifice this creature: It deals damage equal to the number of creatures you control to target creature. - Ability ability = new SimpleActivatedAbility(new DamageTargetEffect( - SourcePermanentPowerValue.NOT_NEGATIVE, "it" - ), new ManaCostsImpl<>("{1}{R}")); + Effect effect = new DamageTargetEffect(CreaturesYouControlCount.instance); + effect.setText("It deals damage equal to the number of creatures you control to target creature"); + Ability ability = new SimpleActivatedAbility(effect, new ManaCostsImpl<>("{1}{R}")); ability.addCost(new SacrificeSourceCost()); ability.addTarget(new TargetCreaturePermanent()); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/s/StarAthlete.java b/Mage.Sets/src/mage/cards/s/StarAthlete.java new file mode 100644 index 00000000000..14cfd2681af --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/StarAthlete.java @@ -0,0 +1,87 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.BlitzAbility; +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.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetNonlandPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class StarAthlete extends CardImpl { + + public StarAthlete(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}{R}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Menace + this.addAbility(new MenaceAbility()); + + // Whenever Star Athlete attacks, choose up to one target nonland permanent. Its controller may sacrifice it. If they don't, Star Athlete deals 5 damage to that player. + Ability ability = new AttacksTriggeredAbility(new StarAthleteEffect()); + ability.addTarget(new TargetNonlandPermanent(0, 1)); + this.addAbility(ability); + + // Blitz {3}{R} + this.addAbility(new BlitzAbility(this, "{3}{R}")); + } + + private StarAthlete(final StarAthlete card) { + super(card); + } + + @Override + public StarAthlete copy() { + return new StarAthlete(this); + } +} + +class StarAthleteEffect extends OneShotEffect { + + StarAthleteEffect() { + super(Outcome.Benefit); + staticText = "choose up to one target nonland permanent. Its controller may sacrifice it. " + + "If they don't, {this} deals 5 damage to that player"; + } + + private StarAthleteEffect(final StarAthleteEffect effect) { + super(effect); + } + + @Override + public StarAthleteEffect copy() { + return new StarAthleteEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent == null) { + return false; + } + Player player = game.getPlayer(permanent.getControllerId()); + return player != null + && (permanent.canBeSacrificed() + && player.chooseUse(outcome, "Sacrifice " + permanent.getName() + " or take 5 damage?", + null, "Sacrifce", "Take 5 damage", source, game) + && permanent.sacrifice(source, game) + || player.damage(5, source, game) > 0); + } +} diff --git a/Mage.Sets/src/mage/cards/s/StartingTown.java b/Mage.Sets/src/mage/cards/s/StartingTown.java new file mode 100644 index 00000000000..b3ca8840ca2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/StartingTown.java @@ -0,0 +1,69 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTappedUnlessAbility; +import mage.abilities.condition.Condition; +import mage.abilities.costs.common.PayLifeCost; +import mage.abilities.mana.AnyColorManaAbility; +import mage.abilities.mana.ColorlessManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.game.Game; +import mage.players.Player; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class StartingTown extends CardImpl { + + public StartingTown(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + this.subtype.add(SubType.TOWN); + + // This land enters tapped unless it's your first, second, or third turn of the game. + this.addAbility(new EntersBattlefieldTappedUnlessAbility(StartingTownCondition.instance)); + + // {T}: Add {C}. + this.addAbility(new ColorlessManaAbility()); + + // {T}, Pay 1 life: Add one mana of any color. + Ability ability = new AnyColorManaAbility(); + ability.addCost(new PayLifeCost(1)); + this.addAbility(ability); + } + + private StartingTown(final StartingTown card) { + super(card); + } + + @Override + public StartingTown copy() { + return new StartingTown(this); + } +} + +enum StartingTownCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + return game.isActivePlayer(source.getControllerId()) + && Optional + .ofNullable(source.getControllerId()) + .map(game::getPlayer) + .map(Player::getTurns) + .map(x -> x <= 3) + .orElse(false); + } + + @Override + public String toString() { + return "it's your first, second, or third turn of the game"; + } +} diff --git a/Mage.Sets/src/mage/cards/s/StasisField.java b/Mage.Sets/src/mage/cards/s/StasisField.java index 2b15b6c9512..ca3e0001842 100644 --- a/Mage.Sets/src/mage/cards/s/StasisField.java +++ b/Mage.Sets/src/mage/cards/s/StasisField.java @@ -4,7 +4,7 @@ import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.common.AttachEffect; -import mage.abilities.effects.common.continuous.SetBasePowerToughnessEnchantedEffect; +import mage.abilities.effects.common.continuous.SetBasePowerToughnessAttachedEffect; import mage.abilities.keyword.DefenderAbility; import mage.abilities.keyword.EnchantAbility; import mage.cards.CardImpl; @@ -36,7 +36,7 @@ public final class StasisField extends CardImpl { this.addAbility(new EnchantAbility(auraTarget)); // Enchanted creature has base power and toughness 0/2, has defender, and loses all other abilities. - Ability ability = new SimpleStaticAbility(new SetBasePowerToughnessEnchantedEffect(0, 2)); + Ability ability = new SimpleStaticAbility(new SetBasePowerToughnessAttachedEffect(0, 2, AttachmentType.AURA)); ability.addEffect(new StasisFieldEffect()); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/s/StillnessInMotion.java b/Mage.Sets/src/mage/cards/s/StillnessInMotion.java index 7a436bf0a0c..ddf6474eb94 100644 --- a/Mage.Sets/src/mage/cards/s/StillnessInMotion.java +++ b/Mage.Sets/src/mage/cards/s/StillnessInMotion.java @@ -46,7 +46,7 @@ class StillnessInMotionEffect extends OneShotEffect { StillnessInMotionEffect() { super(Outcome.Benefit); - staticText = "Then if you have no cards in your library, exile this enchantment " + + staticText = "Then if your library has no cards in it, exile this enchantment " + "and put five cards from your graveyard on top of your library in any order"; } diff --git a/Mage.Sets/src/mage/cards/s/StraxSontaranNurse.java b/Mage.Sets/src/mage/cards/s/StraxSontaranNurse.java new file mode 100644 index 00000000000..d9c5f46a3ac --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/StraxSontaranNurse.java @@ -0,0 +1,112 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DealsDamageToACreatureTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.FightTargetSourceEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.TrampleAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.filter.predicate.permanent.ControllerIdPredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.util.RandomUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class StraxSontaranNurse extends CardImpl { + + public StraxSontaranNurse(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.ALIEN); + this.subtype.add(SubType.CLERIC); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Grenades! -- {2}, {T}, Sacrifice an artifact: Choose a player at random. When you do, Strax fights another target creature that player controls. + Ability ability = new SimpleActivatedAbility(new StraxSontaranNurseEffect(), new GenericManaCost(2)); + ability.addCost(new TapSourceCost()); + ability.addCost(new SacrificeTargetCost(StaticFilters.FILTER_PERMANENT_ARTIFACT_AN)); + this.addAbility(ability.withFlavorWord("Grenades!")); + + // Glory of Battle -- Whenever Strax deals damage to a creature, put a +1/+1 counter on Strax. + this.addAbility(new DealsDamageToACreatureTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), + false, false, false + ).withFlavorWord("Glory of Battle")); + } + + private StraxSontaranNurse(final StraxSontaranNurse card) { + super(card); + } + + @Override + public StraxSontaranNurse copy() { + return new StraxSontaranNurse(this); + } +} + +class StraxSontaranNurseEffect extends OneShotEffect { + + StraxSontaranNurseEffect() { + super(Outcome.Benefit); + staticText = "choose a player at random. When you do, " + + "{this} fights another target creature that player controls"; + } + + private StraxSontaranNurseEffect(final StraxSontaranNurseEffect effect) { + super(effect); + } + + @Override + public StraxSontaranNurseEffect copy() { + return new StraxSontaranNurseEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(RandomUtil.randomFromCollection( + game.getState().getPlayersInRange(source.getControllerId(), game, true) + )); + if (player == null) { + return false; + } + game.informPlayers(player.getLogName() + " has been chosen at random"); + FilterPermanent filter = new FilterCreaturePermanent("another creature controlled by " + player.getName()); + filter.add(AnotherPredicate.instance); + filter.add(new ControllerIdPredicate(player.getId())); + ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility(new FightTargetSourceEffect(), false); + ability.addTarget(new TargetPermanent(filter)); + game.fireReflexiveTriggeredAbility(ability, source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/s/SugarCoat.java b/Mage.Sets/src/mage/cards/s/SugarCoat.java index 03f58ae25ca..dbd0c43030e 100644 --- a/Mage.Sets/src/mage/cards/s/SugarCoat.java +++ b/Mage.Sets/src/mage/cards/s/SugarCoat.java @@ -60,7 +60,7 @@ public final class SugarCoat extends CardImpl { // Based on MinimusContainmentEffect class SugarCoatEffect extends ContinuousEffectImpl { - private static final Ability ability = new FoodAbility(false); + private static final Ability ability = new FoodAbility(); SugarCoatEffect() { super(Duration.WhileOnBattlefield, Outcome.LoseAbility); @@ -112,4 +112,3 @@ class SugarCoatEffect extends ContinuousEffectImpl { return layer == Layer.TypeChangingEffects_4 || layer == Layer.AbilityAddingRemovingEffects_6; } } - diff --git a/Mage.Sets/src/mage/cards/s/SummonAnima.java b/Mage.Sets/src/mage/cards/s/SummonAnima.java new file mode 100644 index 00000000000..e7dbb19025a --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SummonAnima.java @@ -0,0 +1,62 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.SagaAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.LoseLifeOpponentsEffect; +import mage.abilities.effects.common.LoseLifeSourceControllerEffect; +import mage.abilities.effects.common.SacrificeOpponentsEffect; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SagaChapter; +import mage.constants.SubType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SummonAnima extends CardImpl { + + public SummonAnima(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, "{4}{B}{B}"); + + this.subtype.add(SubType.SAGA); + this.subtype.add(SubType.HORROR); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // (As this Saga enters and after your draw step, add a lore counter. Sacrifice after IV.) + SagaAbility sagaAbility = new SagaAbility(this, SagaChapter.CHAPTER_IV); + + // I, II, III -- Pain -- You draw a card and you lose 1 life. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_I, SagaChapter.CHAPTER_III, ability -> { + ability.addEffect(new DrawCardSourceControllerEffect(1, true)); + ability.addEffect(new LoseLifeSourceControllerEffect(1).concatBy("and")); + ability.withFlavorWord("Pain"); + }); + + // IV -- Oblivion -- Each opponent sacrifices a creature of their choice and loses 3 life. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_IV, ability -> { + ability.addEffect(new SacrificeOpponentsEffect(StaticFilters.FILTER_PERMANENT_A_CREATURE)); + ability.addEffect(new LoseLifeOpponentsEffect(3).setText("and loses 3 life")); + ability.withFlavorWord("Oblivion"); + }); + this.addAbility(sagaAbility); + + // Menace + this.addAbility(new MenaceAbility()); + } + + private SummonAnima(final SummonAnima card) { + super(card); + } + + @Override + public SummonAnima copy() { + return new SummonAnima(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SummonBahamut.java b/Mage.Sets/src/mage/cards/s/SummonBahamut.java new file mode 100644 index 00000000000..3312442144b --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SummonBahamut.java @@ -0,0 +1,112 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.SagaAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.DamagePlayersEffect; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SagaChapter; +import mage.constants.SubType; +import mage.constants.TargetController; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.game.Game; +import mage.target.common.TargetNonlandPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SummonBahamut extends CardImpl { + + public SummonBahamut(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, "{9}"); + + this.subtype.add(SubType.SAGA); + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(9); + this.toughness = new MageInt(9); + + // (As this Saga enters and after your draw step, add a lore counter. Sacrifice after IV.) + SagaAbility sagaAbility = new SagaAbility(this, SagaChapter.CHAPTER_IV); + + // I, II -- Destroy up to one target nonland permanent. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_I, SagaChapter.CHAPTER_II, new DestroyTargetEffect(), new TargetNonlandPermanent(0, 1)); + + // III -- Draw two cards. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_III, new DrawCardSourceControllerEffect(2)); + + // IV -- Mega Flare -- This creature deals damage equal to the total mana value of other permanents you control to each opponent. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_IV, ability -> { + ability.addEffect(new DamagePlayersEffect(SummonBahamutValue.instance, TargetController.OPPONENT) + .setText("{this} deals damage equal to the total mana value of other permanents you control to each opponent")); + ability.withFlavorWord("Mega Flare"); + ability.addHint(SummonBahamutValue.getHint()); + }); + this.addAbility(sagaAbility); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + } + + private SummonBahamut(final SummonBahamut card) { + super(card); + } + + @Override + public SummonBahamut copy() { + return new SummonBahamut(this); + } +} + +enum SummonBahamutValue implements DynamicValue { + instance; + private static final FilterPermanent filter = new FilterControlledPermanent(); + + static { + filter.add(AnotherPredicate.instance); + } + + private static final Hint hint = new ValueHint("Total mana value among other permanents you control", instance); + + public static Hint getHint() { + return hint; + } + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return game + .getBattlefield() + .getActivePermanents(filter, sourceAbility.getControllerId(), sourceAbility, game) + .stream() + .mapToInt(MageObject::getManaValue) + .sum(); + } + + @Override + public SummonBahamutValue copy() { + return this; + } + + @Override + public String getMessage() { + return ""; + } + + @Override + public String toString() { + return "1"; + } +} diff --git a/Mage.Sets/src/mage/cards/s/SummonEsperRamuh.java b/Mage.Sets/src/mage/cards/s/SummonEsperRamuh.java new file mode 100644 index 00000000000..35b1615f771 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SummonEsperRamuh.java @@ -0,0 +1,71 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.SagaAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SagaChapter; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.target.common.TargetOpponentsCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SummonEsperRamuh extends CardImpl { + + private static final FilterCard filter = new FilterCard("noncreature, nonland cards"); + private static final FilterCreaturePermanent filter2 = new FilterCreaturePermanent(SubType.WIZARD, "Wizards"); + + static { + filter.add(Predicates.not(CardType.CREATURE.getPredicate())); + filter.add(Predicates.not(CardType.LAND.getPredicate())); + } + + private static final DynamicValue xValue = new CardsInControllerGraveyardCount(filter); + + public SummonEsperRamuh(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, "{2}{R}{R}"); + + this.subtype.add(SubType.SAGA); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // (As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.) + SagaAbility sagaAbility = new SagaAbility(this); + + // I -- Judgment Bolt -- This creature deals damage equal to the number of noncreature, nonland cards in your graveyard to target creature an opponent controls. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_I, ability -> { + ability.addEffect(new DamageTargetEffect(xValue)); + ability.addTarget(new TargetOpponentsCreaturePermanent()); + ability.withFlavorWord("Judgment Bolt"); + }); + + // II, III -- Wizards you control get +1/+0 until end of turn. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_II, SagaChapter.CHAPTER_III, + new BoostControlledEffect(1, 0, Duration.EndOfTurn, filter2) + ); + this.addAbility(sagaAbility); + } + + private SummonEsperRamuh(final SummonEsperRamuh card) { + super(card); + } + + @Override + public SummonEsperRamuh copy() { + return new SummonEsperRamuh(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SummonGoodKingMogXII.java b/Mage.Sets/src/mage/cards/s/SummonGoodKingMogXII.java new file mode 100644 index 00000000000..55312de092b --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SummonGoodKingMogXII.java @@ -0,0 +1,150 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.common.SagaAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.effects.common.CreateTokenCopyTargetEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.LifelinkAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.MoogleToken; +import mage.game.stack.Spell; +import mage.players.Player; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SummonGoodKingMogXII extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledPermanent(SubType.MOOGLE, "other Moogle you control"); + + static { + filter.add(AnotherPredicate.instance); + } + + public SummonGoodKingMogXII(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, "{4}{W}"); + + this.subtype.add(SubType.SAGA); + this.subtype.add(SubType.MOOGLE); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // (As this Saga enters and after your draw step, add a lore counter. Sacrifice after IV.) + SagaAbility sagaAbility = new SagaAbility(this, SagaChapter.CHAPTER_IV); + + // I - Create two 1/2 white Moogle creature tokens with lifelink. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_I, + new CreateTokenEffect(new MoogleToken(), 2) + ); + + // II, III - Whenever you cast a noncreature spell this turn, create a token that's a copy of a non-Saga token you control. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_II, SagaChapter.CHAPTER_III, + new CreateDelayedTriggeredAbilityEffect(new SummonGoodKingMogXIITriggeredAbility()) + ); + + // IV - Put two +1/+1 counters on each other Moogle you control. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_IV, + new AddCountersAllEffect(CounterType.P1P1.createInstance(2), filter) + ); + this.addAbility(sagaAbility); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + } + + private SummonGoodKingMogXII(final SummonGoodKingMogXII card) { + super(card); + } + + @Override + public SummonGoodKingMogXII copy() { + return new SummonGoodKingMogXII(this); + } +} + +class SummonGoodKingMogXIITriggeredAbility extends DelayedTriggeredAbility { + + SummonGoodKingMogXIITriggeredAbility() { + super(new SummonGoodKingMogXIIEffect(), Duration.EndOfTurn, false, false); + setTriggerPhrase("Whenever you cast a noncreature spell this turn, "); + } + + private SummonGoodKingMogXIITriggeredAbility(final SummonGoodKingMogXIITriggeredAbility ability) { + super(ability); + } + + @Override + public SummonGoodKingMogXIITriggeredAbility copy() { + return new SummonGoodKingMogXIITriggeredAbility(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 (!isControlledBy(event.getPlayerId())) { + return false; + } + Spell spell = game.getSpell(event.getTargetId()); + return spell != null && !spell.isCreature(game); + } +} + +class SummonGoodKingMogXIIEffect extends OneShotEffect { + + private static final FilterPermanent filter = new FilterControlledPermanent("non-Saga token you control"); + + SummonGoodKingMogXIIEffect() { + super(Outcome.Benefit); + staticText = "create a token that's a copy of a non-Saga token you control"; + } + + private SummonGoodKingMogXIIEffect(final SummonGoodKingMogXIIEffect effect) { + super(effect); + } + + @Override + public SummonGoodKingMogXIIEffect copy() { + return new SummonGoodKingMogXIIEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null || !game.getBattlefield().contains(filter, source, game, 1)) { + return false; + } + TargetPermanent target = new TargetPermanent(filter); + target.withNotTarget(true); + player.choose(outcome, target, source, game); + Permanent permanent = game.getPermanent(target.getFirstTarget()); + return permanent != null && new CreateTokenCopyTargetEffect().setSavedPermanent(permanent).apply(game, source); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SummonIxion.java b/Mage.Sets/src/mage/cards/s/SummonIxion.java new file mode 100644 index 00000000000..96b1a3884ef --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SummonIxion.java @@ -0,0 +1,63 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.SagaAbility; +import mage.abilities.effects.common.ExileUntilSourceLeavesEffect; +import mage.abilities.effects.common.GainLifeEffect; +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.SagaChapter; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetOpponentsCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SummonIxion extends CardImpl { + + public SummonIxion(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, "{2}{W}"); + + this.subtype.add(SubType.SAGA); + this.subtype.add(SubType.UNICORN); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // (As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.) + SagaAbility sagaAbility = new SagaAbility(this); + + // I - Aerospark -- Exile target creature an opponent controls until this Saga leaves the battlefield. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_I, ability -> { + ability.addEffect(new ExileUntilSourceLeavesEffect()); + ability.addTarget(new TargetOpponentsCreaturePermanent()); + ability.withFlavorWord("Aerospark"); + }); + + // II, III - Put a +1/+1 counter on each of up to two target creatures you control. You gain 2 life. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_II, SagaChapter.CHAPTER_III, ability -> { + ability.addEffect(new AddCountersTargetEffect(CounterType.P1P1.createInstance())); + ability.addEffect(new GainLifeEffect(2)); + ability.addTarget(new TargetControlledCreaturePermanent(0, 2)); + }); + this.addAbility(sagaAbility); + + // First strike + this.addAbility(FirstStrikeAbility.getInstance()); + } + + private SummonIxion(final SummonIxion card) { + super(card); + } + + @Override + public SummonIxion copy() { + return new SummonIxion(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SummonKnightsOfRound.java b/Mage.Sets/src/mage/cards/s/SummonKnightsOfRound.java new file mode 100644 index 00000000000..9b3a4e17b34 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SummonKnightsOfRound.java @@ -0,0 +1,63 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.SagaAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.abilities.keyword.IndestructibleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SagaChapter; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.game.permanent.token.WaylayToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SummonKnightsOfRound extends CardImpl { + + public SummonKnightsOfRound(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, "{6}{W}{W}"); + + this.subtype.add(SubType.SAGA); + this.subtype.add(SubType.KNIGHT); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // (As this Saga enters and after your draw step, add a lore counter. Sacrifice after V.) + SagaAbility sagaAbility = new SagaAbility(this, SagaChapter.CHAPTER_V); + + // I, II, III, IV -- Create three 2/2 white Knight creature tokens. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_I, SagaChapter.CHAPTER_IV, new CreateTokenEffect(new WaylayToken())); + + // V -- Ultimate End -- Other creatures you control get +2/+2 until end of turn. Put an indestructible counter on each of them. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_V, ability -> { + ability.addEffect(new BoostControlledEffect(2, 2, Duration.EndOfTurn)); + ability.addEffect(new AddCountersAllEffect( + CounterType.INDESTRUCTIBLE.createInstance(), + StaticFilters.FILTER_OTHER_CONTROLLED_CREATURES + ).setText("Put an indestructible counter on each of them")); + ability.withFlavorWord("Ultimate End"); + }); + this.addAbility(sagaAbility); + + // Indestructible + this.addAbility(IndestructibleAbility.getInstance()); + } + + private SummonKnightsOfRound(final SummonKnightsOfRound card) { + super(card); + } + + @Override + public SummonKnightsOfRound copy() { + return new SummonKnightsOfRound(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SummonKujata.java b/Mage.Sets/src/mage/cards/s/SummonKujata.java new file mode 100644 index 00000000000..e092714c0f6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SummonKujata.java @@ -0,0 +1,109 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SagaAbility; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DamagePlayersEffect; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.combat.CantBlockTargetEffect; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SummonKujata extends CardImpl { + + public SummonKujata(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, "{5}{R}"); + + this.subtype.add(SubType.SAGA); + this.subtype.add(SubType.OX); + this.power = new MageInt(7); + this.toughness = new MageInt(5); + + // (As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.) + SagaAbility sagaAbility = new SagaAbility(this); + + // I - Lightning -- This creature deals 3 damage to each of up to two target creatures. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_I, ability -> { + ability.addEffect(new DamageTargetEffect(3)); + ability.addTarget(new TargetCreaturePermanent(0, 2)); + ability.withFlavorWord("Lightning"); + }); + + // II - Ice -- Up to three target creatures can't block this turn. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_II, ability -> { + ability.addEffect(new CantBlockTargetEffect(Duration.EndOfTurn)); + ability.addTarget(new TargetCreaturePermanent(0, 3)); + ability.withFlavorWord("Ice"); + }); + + // III - Fire -- Discard a card, then draw two cards. When you discard a card this way, this creature deals damage equal to that card's mana value to each opponent. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_III, ability -> { + ability.addEffect(new SummonKujataEffect()); + ability.withFlavorWord("Fire"); + }); + this.addAbility(sagaAbility); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Haste + this.addAbility(HasteAbility.getInstance()); + } + + private SummonKujata(final SummonKujata card) { + super(card); + } + + @Override + public SummonKujata copy() { + return new SummonKujata(this); + } +} + +class SummonKujataEffect extends OneShotEffect { + + SummonKujataEffect() { + super(Outcome.Benefit); + staticText = "discard a card, then draw two cards. When you discard a card this way, " + + "this creature deals damage equal to that card's mana value to each opponent"; + } + + private SummonKujataEffect(final SummonKujataEffect effect) { + super(effect); + } + + @Override + public SummonKujataEffect copy() { + return new SummonKujataEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Card card = player.discard(1, false, false, source, game).getRandom(game); + player.drawCards(2, source, game); + if (card != null) { + game.fireReflexiveTriggeredAbility(new ReflexiveTriggeredAbility( + new DamagePlayersEffect(card.getManaValue(), TargetController.OPPONENT), false + ), source); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/s/SummonLeviathan.java b/Mage.Sets/src/mage/cards/s/SummonLeviathan.java new file mode 100644 index 00000000000..e3398a1f4a2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SummonLeviathan.java @@ -0,0 +1,107 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.common.SagaAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.ReturnToHandFromBattlefieldAllEffect; +import mage.abilities.keyword.WardAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SagaChapter; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.predicate.Predicates; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SummonLeviathan extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent(); + + static { + filter.add(Predicates.not(SubType.KRAKEN.getPredicate())); + filter.add(Predicates.not(SubType.MERFOLK.getPredicate())); + filter.add(Predicates.not(SubType.OCTOPUS.getPredicate())); + filter.add(Predicates.not(SubType.SERPENT.getPredicate())); + } + + public SummonLeviathan(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, "{4}{U}{U}"); + + this.subtype.add(SubType.SAGA); + this.subtype.add(SubType.LEVIATHAN); + this.power = new MageInt(6); + this.toughness = new MageInt(6); + + // (As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.) + SagaAbility sagaAbility = new SagaAbility(this); + + // I -- Return each creature that isn't a Kraken, Leviathan, Merfolk, Octopus, or Serpent to its owner's hand. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_I, new ReturnToHandFromBattlefieldAllEffect(filter) + .setText("return each creature that isn't a Kraken, Leviathan, Merfolk, Octopus, or Serpent to its owner's hand") + ); + + // II, III -- Until end of turn, whenever a Kraken, Leviathan, Merfolk, Octopus, or Serpent attacks, draw a card. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_II, SagaChapter.CHAPTER_III, + new CreateDelayedTriggeredAbilityEffect(new SummonLeviathanTriggeredAbility()) + ); + this.addAbility(sagaAbility); + + // Ward {2} + this.addAbility(new WardAbility(new ManaCostsImpl<>("{2}"))); + } + + private SummonLeviathan(final SummonLeviathan card) { + super(card); + } + + @Override + public SummonLeviathan copy() { + return new SummonLeviathan(this); + } +} + +class SummonLeviathanTriggeredAbility extends DelayedTriggeredAbility { + + SummonLeviathanTriggeredAbility() { + super(new DrawCardSourceControllerEffect(1), Duration.EndOfTurn, false, false); + this.setTriggerPhrase("Until end of turn, whenever a Kraken, Leviathan, Merfolk, Octopus, or Serpent attacks, "); + } + + private SummonLeviathanTriggeredAbility(final SummonLeviathanTriggeredAbility ability) { + super(ability); + } + + @Override + public SummonLeviathanTriggeredAbility copy() { + return new SummonLeviathanTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ATTACKER_DECLARED; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + Permanent permanent = game.getPermanent(event.getSourceId()); + return permanent != null + && (permanent.hasSubtype(SubType.KRAKEN, game) + || permanent.hasSubtype(SubType.LEVIATHAN, game) + || permanent.hasSubtype(SubType.MERFOLK, game) + || permanent.hasSubtype(SubType.OCTOPUS, game)); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SummonMagusSisters.java b/Mage.Sets/src/mage/cards/s/SummonMagusSisters.java new file mode 100644 index 00000000000..6fe8bfb8de9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SummonMagusSisters.java @@ -0,0 +1,68 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Mode; +import mage.abilities.common.SagaAbility; +import mage.abilities.effects.common.FightTargetSourceEffect; +import mage.abilities.effects.common.GainLifeEffect; +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.SagaChapter; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.target.common.TargetCreaturePermanent; +import mage.target.common.TargetOpponentsCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SummonMagusSisters extends CardImpl { + + public SummonMagusSisters(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, "{4}{G}"); + + this.subtype.add(SubType.SAGA); + this.subtype.add(SubType.FAERIE); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // (As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.) + SagaAbility sagaAbility = new SagaAbility(this); + + // I,II,III - Choose one at random -- + // * Combine Powers! -- Put three +1/+1 counters on target creature. + // * Defense! -- Put a shield counter on target creature. You gain 3 life. + // * Fight! -- This creature fights up to one target creature an opponent controls. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_I, SagaChapter.CHAPTER_III, ability -> { + ability.getModes().setRandom(true); + ability.addEffect(new AddCountersTargetEffect(CounterType.P1P1.createInstance(3))); + ability.addTarget(new TargetCreaturePermanent()); + ability.withFirstModeFlavorWord("Combine Powers!"); + ability.addMode(new Mode(new AddCountersTargetEffect(CounterType.SHIELD.createInstance())) + .addEffect(new GainLifeEffect(3)) + .addTarget(new TargetCreaturePermanent()) + .withFlavorWord("Defense!")); + ability.addMode(new Mode(new FightTargetSourceEffect()) + .addTarget(new TargetOpponentsCreaturePermanent(0, 1)) + .withFlavorWord("Fight!")); + }); + this.addAbility(sagaAbility); + + // Haste + this.addAbility(HasteAbility.getInstance()); + } + + private SummonMagusSisters(final SummonMagusSisters card) { + super(card); + } + + @Override + public SummonMagusSisters copy() { + return new SummonMagusSisters(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SummonPrimalGaruda.java b/Mage.Sets/src/mage/cards/s/SummonPrimalGaruda.java new file mode 100644 index 00000000000..fc19158781b --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SummonPrimalGaruda.java @@ -0,0 +1,73 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.SagaAbility; +import mage.abilities.effects.common.DamageTargetEffect; +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.SagaChapter; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterOpponentsCreaturePermanent; +import mage.filter.predicate.permanent.TappedPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SummonPrimalGaruda extends CardImpl { + + private static final FilterPermanent filter + = new FilterOpponentsCreaturePermanent("tapped creature an opponent controls"); + + static { + filter.add(TappedPredicate.TAPPED); + } + + public SummonPrimalGaruda(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, "{3}{W}"); + + this.subtype.add(SubType.SAGA); + this.subtype.add(SubType.HARPY); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // (As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.) + SagaAbility sagaAbility = new SagaAbility(this); + + // I -- Aerial Blast -- This creature deals 4 damage to target tapped creature an opponent controls. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_I, ability -> { + ability.addEffect(new DamageTargetEffect(4)); + ability.addTarget(new TargetPermanent(filter)); + ability.withFlavorWord("Aerial Blast"); + }); + + // II, III -- Slipstream -- Another target creature you control gets +1/+0 and gains flying until end of turn. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_II, SagaChapter.CHAPTER_III, ability -> { + ability.addEffect(new BoostTargetEffect(2, 0).setText("another target creature you control gets +1/+0")); + ability.addEffect(new GainAbilityTargetEffect(FlyingAbility.getInstance()).setText("and gains flying until end of turn")); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_ANOTHER_TARGET_CREATURE_YOU_CONTROL)); + ability.withFlavorWord("Slipstream"); + }); + this.addAbility(sagaAbility); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + } + + private SummonPrimalGaruda(final SummonPrimalGaruda card) { + super(card); + } + + @Override + public SummonPrimalGaruda copy() { + return new SummonPrimalGaruda(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SummonPrimalOdin.java b/Mage.Sets/src/mage/cards/s/SummonPrimalOdin.java new file mode 100644 index 00000000000..4d65029bb48 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SummonPrimalOdin.java @@ -0,0 +1,71 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.common.SagaAbility; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.LoseGameTargetPlayerEffect; +import mage.abilities.effects.common.LoseLifeAllPlayersEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SagaChapter; +import mage.constants.SubType; +import mage.target.common.TargetOpponentsCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SummonPrimalOdin extends CardImpl { + + public SummonPrimalOdin(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, "{4}{B}{B}"); + + this.subtype.add(SubType.SAGA); + this.subtype.add(SubType.KNIGHT); + this.power = new MageInt(5); + this.toughness = new MageInt(3); + + // (As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.) + SagaAbility sagaAbility = new SagaAbility(this); + + // I -- Gungnir -- Destroy target creature an opponent controls. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_I, ability -> { + ability.addEffect(new DestroyTargetEffect()); + ability.addTarget(new TargetOpponentsCreaturePermanent()); + ability.withFlavorWord("Gungnir"); + }); + + // II -- Zantetsuken -- This creature gains "Whenever this creature deals combat damage to a player, that player loses the game." + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_II, ability -> { + ability.addEffect(new GainAbilitySourceEffect( + new DealsCombatDamageToAPlayerTriggeredAbility( + new LoseGameTargetPlayerEffect(), false, true + ), Duration.Custom + )); + ability.withFlavorWord("Zantetsuken"); + }); + + // III -- Hall of Sorrow -- Draw two cards. Each player loses 2 life. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_III, ability -> { + ability.addEffect(new DrawCardSourceControllerEffect(2)); + ability.addEffect(new LoseLifeAllPlayersEffect(2)); + ability.withFlavorWord("Hall of Sorrow"); + }); + this.addAbility(sagaAbility); + } + + private SummonPrimalOdin(final SummonPrimalOdin card) { + super(card); + } + + @Override + public SummonPrimalOdin copy() { + return new SummonPrimalOdin(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SummonYojimbo.java b/Mage.Sets/src/mage/cards/s/SummonYojimbo.java new file mode 100644 index 00000000000..7ac21cbea3b --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SummonYojimbo.java @@ -0,0 +1,124 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SagaAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.effects.common.combat.CantAttackYouUnlessPayAllEffect; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterOpponentsCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.PowerPredicate; +import mage.filter.predicate.permanent.TappedPredicate; +import mage.game.Controllable; +import mage.game.Game; +import mage.game.permanent.token.TreasureToken; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SummonYojimbo extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("artifact, enchantment, or tapped creature an opponent controls"); + + static { + filter.add(Predicates.or( + CardType.ARTIFACT.getPredicate(), + CardType.ENCHANTMENT.getPredicate(), + Predicates.and( + TappedPredicate.TAPPED, + CardType.CREATURE.getPredicate() + ) + )); + filter.add(TargetController.OPPONENT.getControllerPredicate()); + } + + public SummonYojimbo(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, "{3}{W}"); + + this.subtype.add(SubType.SAGA); + this.subtype.add(SubType.SAMURAI); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // (As this Saga enters and after your draw step, add a lore counter. Sacrifice after IV.) + SagaAbility sagaAbility = new SagaAbility(this, SagaChapter.CHAPTER_IV); + + // I - Exile target artifact, enchantment, or tapped creature an opponent controls. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_I, + new ExileTargetEffect(), new TargetPermanent(filter) + ); + + // II, III - Until your next turn, creatures can't attack you unless their controller pays {2} for each of those creatures. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_II, SagaChapter.CHAPTER_III, + new CantAttackYouUnlessPayAllEffect(Duration.UntilYourNextTurn, new GenericManaCost(2)) + ); + + // IV - Create X Treasure tokens, where X is the number of opponents who control a creature with power 4 or greater. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_IV, + new CreateTokenEffect(new TreasureToken(), SummonYojimboValue.instance) + ); + this.addAbility(sagaAbility); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + } + + private SummonYojimbo(final SummonYojimbo card) { + super(card); + } + + @Override + public SummonYojimbo copy() { + return new SummonYojimbo(this); + } +} + +enum SummonYojimboValue implements DynamicValue { + instance; + private static final FilterPermanent filter = new FilterOpponentsCreaturePermanent(); + + static { + filter.add(new PowerPredicate(ComparisonType.MORE_THAN, 3)); + } + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return game.getBattlefield() + .getActivePermanents(filter, sourceAbility.getControllerId(), sourceAbility, game) + .stream() + .map(Controllable::getControllerId) + .distinct() + .mapToInt(x -> 1) + .sum(); + } + + @Override + public SummonYojimboValue copy() { + return this; + } + + @Override + public String getMessage() { + return "the number of opponents who control a creature with power 4 or greater"; + } + + @Override + public String toString() { + return "X"; + } +} diff --git a/Mage.Sets/src/mage/cards/s/SummonersGrimoire.java b/Mage.Sets/src/mage/cards/s/SummonersGrimoire.java new file mode 100644 index 00000000000..7f5265668e0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SummonersGrimoire.java @@ -0,0 +1,96 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.AddCardSubtypeAttachedEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.keyword.EquipAbility; +import mage.abilities.keyword.JobSelectAbility; +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.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInHand; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SummonersGrimoire extends CardImpl { + + public SummonersGrimoire(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}{G}"); + + this.subtype.add(SubType.EQUIPMENT); + + // Job select + this.addAbility(new JobSelectAbility()); + + // Equipped creature is a Shaman in addition to its other types and has "Whenever this creature attacks, you may put a creature card from your hand onto the battlefield. If that card is an enchantment card, it enters tapped and attacking." + Ability ability = new SimpleStaticAbility(new AddCardSubtypeAttachedEffect( + SubType.SHAMAN, AttachmentType.EQUIPMENT + ).setText("equipped creature is a Shaman in addition to its other types")); + ability.addEffect(new GainAbilityAttachedEffect( + new AttacksTriggeredAbility(new SummonersGrimoireEffect()), AttachmentType.EQUIPMENT + ).setText("and has \"Whenever this creature attacks, you may put a creature card from your hand " + + "onto the battlefield. If that card is an enchantment card, it enters tapped and attacking.\"")); + this.addAbility(ability); + + // Abraxas -- Equip {3} + this.addAbility(new EquipAbility(3).withFlavorWord("Abraxas")); + } + + private SummonersGrimoire(final SummonersGrimoire card) { + super(card); + } + + @Override + public SummonersGrimoire copy() { + return new SummonersGrimoire(this); + } +} + +class SummonersGrimoireEffect extends OneShotEffect { + + SummonersGrimoireEffect() { + super(Outcome.Benefit); + staticText = "you may put a creature card from your hand onto the battlefield. " + + "If that card is an enchantment card, it enters tapped and attacking"; + } + + private SummonersGrimoireEffect(final SummonersGrimoireEffect effect) { + super(effect); + } + + @Override + public SummonersGrimoireEffect copy() { + return new SummonersGrimoireEffect(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); + player.choose(outcome, player.getHand(), target, source, game); + Card card = game.getCard(target.getFirstTarget()); + if (card == null) { + return false; + } + boolean flag = card.isEnchantment(game); + player.moveCards(card, Zone.BATTLEFIELD, source, game, flag, false, false, null); + if (flag) { + game.getCombat().addAttackingCreature(card.getId(), game); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/s/SummonersSending.java b/Mage.Sets/src/mage/cards/s/SummonersSending.java new file mode 100644 index 00000000000..170a62a74a7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SummonersSending.java @@ -0,0 +1,85 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +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.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.token.SpiritWhiteToken; +import mage.game.permanent.token.Token; +import mage.players.Player; +import mage.target.common.TargetCardInGraveyard; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SummonersSending extends CardImpl { + + public SummonersSending(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{W}"); + + // At the beginning of your end step, you may exile target creature card from a graveyard. If you do, create a 1/1 white Spirit creature token with flying. Put a +1/+1 counter on it if the exiled card's mana value is 4 or greater. + Ability ability = new BeginningOfEndStepTriggeredAbility(new SummonersSendingEffect(), true); + ability.addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE)); + this.addAbility(ability); + } + + private SummonersSending(final SummonersSending card) { + super(card); + } + + @Override + public SummonersSending copy() { + return new SummonersSending(this); + } +} + +class SummonersSendingEffect extends OneShotEffect { + + SummonersSendingEffect() { + super(Outcome.Benefit); + staticText = "exile target creature card from a graveyard. If you do, create a " + + "1/1 white Spirit creature token with flying. Put a +1/+1 counter on it " + + "if the exiled card's mana value is 4 or greater"; + } + + private SummonersSendingEffect(final SummonersSendingEffect effect) { + super(effect); + } + + @Override + public SummonersSendingEffect copy() { + return new SummonersSendingEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); + if (player == null || card == null) { + return false; + } + player.moveCards(card, Zone.EXILED, source, game); + Token token = new SpiritWhiteToken(); + token.putOntoBattlefield(1, game, source); + if (card.getManaValue() < 4) { + return true; + } + for (UUID tokenId : token.getLastAddedTokenIds()) { + Optional.ofNullable(tokenId) + .map(game::getPermanent) + .ifPresent(permanent -> permanent.addCounters(CounterType.P1P1.createInstance(), source, game)); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/s/SummoningMateria.java b/Mage.Sets/src/mage/cards/s/SummoningMateria.java new file mode 100644 index 00000000000..b7cd8a28f80 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SummoningMateria.java @@ -0,0 +1,66 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.AttachedToMatchesFilterCondition; +import mage.abilities.decorator.ConditionalAsThoughEffect; +import mage.abilities.effects.common.continuous.BoostEquippedEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; +import mage.abilities.effects.common.continuous.PlayFromTopOfLibraryEffect; +import mage.abilities.keyword.EquipAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.abilities.mana.GreenManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AttachmentType; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SummoningMateria extends CardImpl { + + private static final Condition condition = new AttachedToMatchesFilterCondition(StaticFilters.FILTER_PERMANENT_CREATURE); + + public SummoningMateria(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{G}"); + + this.subtype.add(SubType.EQUIPMENT); + + // You may look at the top card of your library any time. + this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); + + // As long as this Equipment is attached to a creature, you may cast creature spells from the top of your library. + this.addAbility(new SimpleStaticAbility(new ConditionalAsThoughEffect( + new PlayFromTopOfLibraryEffect(StaticFilters.FILTER_CARD_CREATURE), condition + ).setText("as long as {this} is attached to a creature, you may cast creature spells from the top of your library"))); + + // Equipped creature gets +2/+2 and has vigilance and "{T}: Add {G}." + Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(2, 2)); + ability.addEffect(new GainAbilityAttachedEffect( + VigilanceAbility.getInstance(), AttachmentType.EQUIPMENT + ).setText("and has vigilance")); + ability.addEffect(new GainAbilityAttachedEffect( + new GreenManaAbility(), AttachmentType.EQUIPMENT + ).setText("and {T}: Add {G}.")); + this.addAbility(ability); + + // Equip {2} + this.addAbility(new EquipAbility(2)); + } + + private SummoningMateria(final SummoningMateria card) { + super(card); + } + + @Override + public SummoningMateria copy() { + return new SummoningMateria(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SunBlessedHealer.java b/Mage.Sets/src/mage/cards/s/SunBlessedHealer.java index daa65a2adc8..8690d3ca173 100644 --- a/Mage.Sets/src/mage/cards/s/SunBlessedHealer.java +++ b/Mage.Sets/src/mage/cards/s/SunBlessedHealer.java @@ -51,7 +51,7 @@ public final class SunBlessedHealer extends CardImpl { Ability ability = new EntersBattlefieldTriggeredAbility( new ReturnFromGraveyardToBattlefieldTargetEffect() ).withInterveningIf(KickedCondition.ONCE); - ability.addTarget(new TargetCardInYourGraveyard()); + ability.addTarget(new TargetCardInYourGraveyard(filter)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/s/Suplex.java b/Mage.Sets/src/mage/cards/s/Suplex.java new file mode 100644 index 00000000000..26c16801e78 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/Suplex.java @@ -0,0 +1,41 @@ +package mage.cards.s; + +import mage.abilities.Mode; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.effects.common.ExileTargetIfDiesEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.target.common.TargetArtifactPermanent; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Suplex extends CardImpl { + + public Suplex(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{R}"); + + // Choose one -- + // * Suplex deals 3 damage to target creature. If that creature would die this turn, exile it instead. + this.getSpellAbility().addEffect(new DamageTargetEffect(3)); + this.getSpellAbility().addEffect(new ExileTargetIfDiesEffect()); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + + // * Exile target artifact. + this.getSpellAbility().addMode(new Mode(new ExileTargetEffect()).addTarget(new TargetArtifactPermanent())); + } + + private Suplex(final Suplex card) { + super(card); + } + + @Override + public Suplex copy() { + return new Suplex(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SuppressionRay.java b/Mage.Sets/src/mage/cards/s/SuppressionRay.java index 476e96b6dd7..ddb25bec8de 100644 --- a/Mage.Sets/src/mage/cards/s/SuppressionRay.java +++ b/Mage.Sets/src/mage/cards/s/SuppressionRay.java @@ -114,6 +114,7 @@ class SuppressionRayTargetEffect extends OneShotEffect { int numberToPay = controller.getAmount( 0, maxEnergy, "How many {E} do you like to pay? (" + tappedThisWay.size() + " creature(s) were tapped)", + source, game ); if (numberToPay == 0) { diff --git a/Mage.Sets/src/mage/cards/s/SurgeOfSalvation.java b/Mage.Sets/src/mage/cards/s/SurgeOfSalvation.java index ab05786ef90..dd580ee3392 100644 --- a/Mage.Sets/src/mage/cards/s/SurgeOfSalvation.java +++ b/Mage.Sets/src/mage/cards/s/SurgeOfSalvation.java @@ -1,24 +1,23 @@ package mage.cards.s; -import java.util.UUID; - import mage.MageObject; import mage.abilities.Ability; import mage.abilities.effects.PreventionEffectImpl; import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; import mage.abilities.effects.common.continuous.GainAbilityControllerEffect; -import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; import mage.abilities.keyword.HexproofAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; +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 SurgeOfSalvation extends CardImpl { @@ -29,7 +28,11 @@ public final class SurgeOfSalvation extends CardImpl { // You and permanents you control gain hexproof until end of turn. Prevent all damage that black and/or red sources would deal to creatures you control this turn. this.getSpellAbility().addEffect(new GainAbilityControllerEffect( HexproofAbility.getInstance(), Duration.EndOfTurn - ).setText("you"));this.getSpellAbility().addEffect(new GainAbilityControlledEffect(HexproofAbility.getInstance(),Duration.EndOfTurn).concatBy("and"));this.getSpellAbility().addEffect(new SurgeOfSalvationEffect()); + ).setText("you")); + this.getSpellAbility().addEffect(new GainAbilityControlledEffect( + HexproofAbility.getInstance(), Duration.EndOfTurn, StaticFilters.FILTER_PERMANENTS + ).concatBy("and")); + this.getSpellAbility().addEffect(new SurgeOfSalvationEffect()); } private SurgeOfSalvation(final SurgeOfSalvation card) { @@ -45,7 +48,7 @@ public final class SurgeOfSalvation extends CardImpl { class SurgeOfSalvationEffect extends PreventionEffectImpl { SurgeOfSalvationEffect() { - super(Duration.EndOfTurn,Integer.MAX_VALUE,false); + super(Duration.EndOfTurn, Integer.MAX_VALUE, false); staticText = "Prevent all damage that black and/or red sources would deal to creatures you control this turn"; } @@ -60,16 +63,16 @@ class SurgeOfSalvationEffect extends PreventionEffectImpl { @Override public boolean checksEventType(GameEvent event, Game game) { - return event.getType()== GameEvent.EventType.DAMAGE_PERMANENT; + return event.getType() == GameEvent.EventType.DAMAGE_PERMANENT; } @Override public boolean applies(GameEvent event, Ability source, Game game) { - if(! super.applies(event, source, game)){ + if (!super.applies(event, source, game)) { return false; } - Permanent permanent=game.getPermanent(event.getTargetId()); - MageObject sourceObject=game.getObject(event.getSourceId()); + Permanent permanent = game.getPermanent(event.getTargetId()); + MageObject sourceObject = game.getObject(event.getSourceId()); return permanent != null && sourceObject != null && permanent.isCreature(game) diff --git a/Mage.Sets/src/mage/cards/s/Suspend.java b/Mage.Sets/src/mage/cards/s/Suspend.java index 3f59bbf0d83..6a0cad5b8c8 100644 --- a/Mage.Sets/src/mage/cards/s/Suspend.java +++ b/Mage.Sets/src/mage/cards/s/Suspend.java @@ -1,9 +1,7 @@ package mage.cards.s; -import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.continuous.GainSuspendEffect; import mage.abilities.keyword.SuspendAbility; import mage.cards.Card; import mage.cards.CardImpl; @@ -11,7 +9,6 @@ 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; @@ -62,27 +59,13 @@ class SuspendEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Permanent permanent = game.getPermanent(source.getFirstTarget()); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (controller == null || permanent == null) { return false; } Card card = permanent.getMainCard(); - if (!controller.moveCards(permanent, Zone.EXILED, source, game) - || game.getState().getZone(card.getId()) != Zone.EXILED) { - return true; - } - UUID exileId = SuspendAbility.getSuspendExileId(controller.getId(), game); - if (!controller.moveCardToExileWithInfo( - card, exileId, "Suspended cards of " + controller.getName(), - source, game, Zone.HAND, true - )) { - return true; - } - card.addCounters(CounterType.TIME.createInstance(2), source.getControllerId(), source, game); - if (!card.getAbilities(game).containsClass(SuspendAbility.class)) { - game.addEffect(new GainSuspendEffect(new MageObjectReference(card, game)), source); - } - game.informPlayers(controller.getLogName() + " suspends 2 - " + card.getName()); - return true; + return controller.moveCards(permanent, Zone.EXILED, source, game) + && game.getState().getZone(card.getId()) == Zone.EXILED + && SuspendAbility.addTimeCountersAndSuspend(card, 3, source, game); } } diff --git a/Mage.Sets/src/mage/cards/s/SwordOfTheSqueak.java b/Mage.Sets/src/mage/cards/s/SwordOfTheSqueak.java new file mode 100644 index 00000000000..f0dc00a87bf --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SwordOfTheSqueak.java @@ -0,0 +1,73 @@ +package mage.cards.s; + +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.continuous.BoostEquippedEffect; +import mage.abilities.keyword.EquipAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.BasePowerPredicate; +import mage.filter.predicate.mageobject.BaseToughnessPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SwordOfTheSqueak extends CardImpl { + + private static final FilterPermanent filter + = new FilterControlledCreaturePermanent("creature you control with base power or toughness 1"); + private static final FilterPermanent filter2 + = new FilterControlledPermanent("a Hamster, Mouse, Rat, or Squirrel you control"); + + static { + filter.add(Predicates.or( + new BasePowerPredicate(ComparisonType.EQUAL_TO, 1), + new BaseToughnessPredicate(ComparisonType.EQUAL_TO, 1) + )); + filter2.add(Predicates.or( + SubType.HAMSTER.getPredicate(), + SubType.MOUSE.getPredicate(), + SubType.RAT.getPredicate(), + SubType.SQUIRREL.getPredicate() + )); + } + + private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter, 1); + + public SwordOfTheSqueak(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); + + this.subtype.add(SubType.EQUIPMENT); + + // Equipped creature gets +1/+1 for each creature you control with base power or toughness 1. + this.addAbility(new SimpleStaticAbility(new BoostEquippedEffect(xValue, xValue))); + + // Whenever a Hamster, Mouse, Rat, or Squirrel you control enters, you may attach Sword of the Squeak to that creature. + this.addAbility(new EntersBattlefieldAllTriggeredAbility( + Zone.BATTLEFIELD, new AttachEffect(Outcome.BoostCreature, "attach {this} to that creature"), + filter2, true, SetTargetPointer.PERMANENT + )); + + // Equip {2} + this.addAbility(new EquipAbility(2)); + } + + private SwordOfTheSqueak(final SwordOfTheSqueak card) { + super(card); + } + + @Override + public SwordOfTheSqueak copy() { + return new SwordOfTheSqueak(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SycoraxCommander.java b/Mage.Sets/src/mage/cards/s/SycoraxCommander.java new file mode 100644 index 00000000000..db301e02c29 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SycoraxCommander.java @@ -0,0 +1,92 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.FaceVillainousChoiceOpponentsEffect; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.abilities.keyword.HasteAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.choices.FaceVillainousChoice; +import mage.choices.VillainousChoice; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.game.Game; +import mage.players.Player; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SycoraxCommander extends CardImpl { + + private static final FaceVillainousChoice choice = new FaceVillainousChoice( + Outcome.Discard, new SycoraxCommanderFirstChoice(), new SycoraxCommanderSecondChoice() + ); + + public SycoraxCommander(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}{R}"); + + this.subtype.add(SubType.ALIEN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(4); + this.toughness = new MageInt(2); + + // First strike + this.addAbility(FirstStrikeAbility.getInstance()); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // Sanctified Rules of Combat -- When Sycorax Commander enters the battlefield, each opponent faces a villainous choice -- That opponent discards all the cards in their hand, then draws that many cards minus one, or Sycorax Commander deals damage to that player equal to the number of cards in their hand. + this.addAbility(new EntersBattlefieldTriggeredAbility( + new FaceVillainousChoiceOpponentsEffect(choice) + ).withFlavorWord("Sanctified Rules of Combat")); + } + + private SycoraxCommander(final SycoraxCommander card) { + super(card); + } + + @Override + public SycoraxCommander copy() { + return new SycoraxCommander(this); + } +} + +class SycoraxCommanderFirstChoice extends VillainousChoice { + + SycoraxCommanderFirstChoice() { + super( + "That opponent discards all the cards in their hand, then draws that many cards minus one", + "Discard your hand and draw one less card" + ); + } + + @Override + public boolean doChoice(Player player, Game game, Ability source) { + int discarded = player.discard(player.getHand(), false, source, game).size(); + if (discarded > 0) { + player.drawCards(discarded - 1, source, game); + } + return true; + } +} + +class SycoraxCommanderSecondChoice extends VillainousChoice { + + SycoraxCommanderSecondChoice() { + super( + "{this} deals damage to that player equal to the number of cards in their hand", + "Take damage equal to the number of cards in your hand" + ); + } + + @Override + public boolean doChoice(Player player, Game game, Ability source) { + return player.damage(player.getHand().size(), source, game) > 0; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TaigamMasterOpportunist.java b/Mage.Sets/src/mage/cards/t/TaigamMasterOpportunist.java index 025b1df41c3..76724079539 100644 --- a/Mage.Sets/src/mage/cards/t/TaigamMasterOpportunist.java +++ b/Mage.Sets/src/mage/cards/t/TaigamMasterOpportunist.java @@ -1,31 +1,28 @@ package mage.cards.t; -import java.util.UUID; import mage.MageInt; -import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.common.FlurryAbility; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.continuous.GainSuspendEffect; import mage.abilities.keyword.SuspendAbility; import mage.cards.Card; -import mage.constants.*; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.counters.CounterType; +import mage.constants.*; import mage.game.Game; import mage.game.stack.Spell; import mage.players.Player; +import java.util.UUID; + /** - * * @author Jmlundeen */ public final class TaigamMasterOpportunist extends CardImpl { public TaigamMasterOpportunist(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}"); - + this.supertype.add(SuperType.LEGENDARY); this.subtype.add(SubType.HUMAN); this.subtype.add(SubType.MONK); @@ -65,30 +62,18 @@ class TaigamMasterOpportunistEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Spell spell = (Spell) this.getValue("spellCast"); Player controller = game.getPlayer(source.getControllerId()); - if (spell == null || controller == null) { + Spell spell = (Spell) this.getValue("spellCast"); + if (controller == null || spell == null) { return false; } - // copy it spell.createCopyOnStack(game, source, source.getControllerId(), false); // exile it, if it doesn't have suspend, it gains suspend // get main card to work with adventure/omen/split Card card = spell.getMainCard(); - if (card == null) { - return false; - } - UUID exileId = SuspendAbility.getSuspendExileId(controller.getId(), game); - if (controller.moveCardsToExile(card, source, game, true, exileId, "Suspended cards of " + controller.getName())) { - boolean hasSuspend = card.getAbilities(game).containsClass(SuspendAbility.class); - card.addCounters(CounterType.TIME.createInstance(4), source, game); - game.informPlayers(controller.getLogName() + " exiles " + spell.getLogName() + " with 3 time counters on it"); - if (!hasSuspend) { - game.addEffect(new GainSuspendEffect(new MageObjectReference(card, game)), source); - } - return true; - } - return false; + controller.moveCards(card, Zone.EXILED, source, game); + SuspendAbility.addTimeCountersAndSuspend(card, 4, source, game); + return true; } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/t/TalionTheKindlyLord.java b/Mage.Sets/src/mage/cards/t/TalionTheKindlyLord.java index ba8037a083a..c6d438b3499 100644 --- a/Mage.Sets/src/mage/cards/t/TalionTheKindlyLord.java +++ b/Mage.Sets/src/mage/cards/t/TalionTheKindlyLord.java @@ -109,7 +109,7 @@ class TalionTheKindlyLordEffect extends OneShotEffect { if (controller == null) { return true; } - int numberChoice = controller.getAmount(1, 10, "Choose a number.", game); + int numberChoice = controller.getAmount(1, 10, "Choose a number.", source, game); game.getState().setValue("chosenNumber_" + source.getSourceId() + '_' + source.getSourceObjectZoneChangeCounter(), numberChoice); Permanent permanent = game.getPermanentEntering(source.getSourceId()); diff --git a/Mage.Sets/src/mage/cards/t/TataruTaru.java b/Mage.Sets/src/mage/cards/t/TataruTaru.java new file mode 100644 index 00000000000..e9b72ed0ea7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TataruTaru.java @@ -0,0 +1,98 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DrawCardOpponentTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.game.Game; +import mage.game.permanent.token.TreasureToken; +import mage.target.common.TargetOpponent; +import mage.util.CardUtil; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TataruTaru extends CardImpl { + + public TataruTaru(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.DWARF); + this.subtype.add(SubType.ADVISOR); + this.power = new MageInt(0); + this.toughness = new MageInt(3); + + // When Tataru Taru enters, you draw a card and target opponent may draw a card. + Ability ability = new EntersBattlefieldTriggeredAbility(new DrawCardSourceControllerEffect(1)); + ability.addEffect(new TataruTaruEffect()); + ability.addTarget(new TargetOpponent()); + this.addAbility(ability); + + // Scions' Secretary -- Whenever an opponent draws a card, if it isn't that player's turn, create a tapped Treasure token. This ability triggers only once each turn. + this.addAbility(new DrawCardOpponentTriggeredAbility( + new CreateTokenEffect(new TreasureToken(), 1, true), false, false + ).setTriggersLimitEachTurn(1).withInterveningIf(TataruTaruCondition.instance).withFlavorWord("Scions' Secretary")); + } + + private TataruTaru(final TataruTaru card) { + super(card); + } + + @Override + public TataruTaru copy() { + return new TataruTaru(this); + } +} + +enum TataruTaruCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + return !CardUtil + .getEffectValueFromAbility(source, "playerDrew", UUID.class) + .map(game::isActivePlayer) + .orElse(true); + } +} + +class TataruTaruEffect extends OneShotEffect { + + TataruTaruEffect() { + super(Outcome.Benefit); + staticText = "and target opponent may draw a card"; + } + + private TataruTaruEffect(final TataruTaruEffect effect) { + super(effect); + } + + @Override + public TataruTaruEffect copy() { + return new TataruTaruEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return Optional + .ofNullable(getTargetPointer().getFirst(game, source)) + .map(game::getPlayer) + .filter(player -> player.chooseUse(Outcome.DrawCard, "Draw a card?", source, game)) + .map(player -> player.drawCards(1, source, game)) + .orElse(0) > 0; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TavernScoundrel.java b/Mage.Sets/src/mage/cards/t/TavernScoundrel.java index cb37ed04daf..d60a97629f8 100644 --- a/Mage.Sets/src/mage/cards/t/TavernScoundrel.java +++ b/Mage.Sets/src/mage/cards/t/TavernScoundrel.java @@ -2,8 +2,8 @@ package mage.cards.t; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.WonCoinFlipControllerTriggeredAbility; import mage.abilities.costs.common.SacrificeTargetCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.GenericManaCost; @@ -13,14 +13,9 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.mageobject.AnotherPredicate; -import mage.game.Game; -import mage.game.events.CoinFlippedEvent; -import mage.game.events.GameEvent; import mage.game.permanent.token.TreasureToken; -import mage.target.common.TargetControlledPermanent; import java.util.UUID; @@ -44,7 +39,7 @@ public final class TavernScoundrel extends CardImpl { this.toughness = new MageInt(3); // Whenever you win a coin flip, create two Treasure tokens. - this.addAbility(new TavernScoundrelTriggeredAbility()); + this.addAbility(new WonCoinFlipControllerTriggeredAbility(new CreateTokenEffect(new TreasureToken(), 2))); // {1}, {T}, Sacrifice another permanent: Flip a coin. Ability ability = new SimpleActivatedAbility(new FlipCoinEffect(), new GenericManaCost(1)); @@ -62,37 +57,3 @@ public final class TavernScoundrel extends CardImpl { return new TavernScoundrel(this); } } - -class TavernScoundrelTriggeredAbility extends TriggeredAbilityImpl { - - TavernScoundrelTriggeredAbility() { - super(Zone.BATTLEFIELD, new CreateTokenEffect(new TreasureToken(), 2), false); - } - - private TavernScoundrelTriggeredAbility(final TavernScoundrelTriggeredAbility ability) { - super(ability); - } - - @Override - public TavernScoundrelTriggeredAbility copy() { - return new TavernScoundrelTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.COIN_FLIPPED; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - CoinFlippedEvent flipEvent = (CoinFlippedEvent) event; - return isControlledBy(event.getPlayerId()) - && flipEvent.isWinnable() - && flipEvent.wasWon(); - } - - @Override - public String getRule() { - return "Whenever you win a coin flip, create two Treasure tokens."; - } -} diff --git a/Mage.Sets/src/mage/cards/t/TemporaryTruce.java b/Mage.Sets/src/mage/cards/t/TemporaryTruce.java index 10b6092c579..92f9a43e6c6 100644 --- a/Mage.Sets/src/mage/cards/t/TemporaryTruce.java +++ b/Mage.Sets/src/mage/cards/t/TemporaryTruce.java @@ -57,7 +57,7 @@ class TemporaryTruceEffect extends OneShotEffect { for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { Player player = game.getPlayer(playerId); if (player != null) { - int cardsToDraw = player.getAmount(0, 2, "Draw how many cards?", game); + int cardsToDraw = player.getAmount(0, 2, "Draw how many cards?", source, game); player.drawCards(cardsToDraw, source, game); player.gainLife((2 - cardsToDraw) * 2, game, source); } diff --git a/Mage.Sets/src/mage/cards/t/TemptWithMayhem.java b/Mage.Sets/src/mage/cards/t/TemptWithMayhem.java new file mode 100644 index 00000000000..f8a6e528c62 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TemptWithMayhem.java @@ -0,0 +1,80 @@ +package mage.cards.t; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AbilityWord; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.stack.Spell; +import mage.players.Player; +import mage.target.TargetSpell; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TemptWithMayhem extends CardImpl { + + public TemptWithMayhem(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{R}{R}"); + + // Tempting offer -- Choose target instant or sorcery spell. Each opponent may copy that spell and may choose new targets for the copy they control. You copy that spell once plus an additional time for each opponent who copied the spell this way. You may choose new targets for the copies you control. + this.getSpellAbility().addEffect(new TemptWithMayhemEffect()); + this.getSpellAbility().addTarget(new TargetSpell(StaticFilters.FILTER_SPELL_INSTANT_OR_SORCERY)); + this.getSpellAbility().setAbilityWord(AbilityWord.TEMPTING_OFFER); + } + + private TemptWithMayhem(final TemptWithMayhem card) { + super(card); + } + + @Override + public TemptWithMayhem copy() { + return new TemptWithMayhem(this); + } +} + +class TemptWithMayhemEffect extends OneShotEffect { + + TemptWithMayhemEffect() { + super(Outcome.Benefit); + staticText = "choose target instant or sorcery spell. Each opponent may copy that spell " + + "and may choose new targets for the copy they control. You copy that spell once " + + "plus an additional time for each opponent who copied the spell this way. " + + "You may choose new targets for the copies you control"; + } + + private TemptWithMayhemEffect(final TemptWithMayhemEffect effect) { + super(effect); + } + + @Override + public TemptWithMayhemEffect copy() { + return new TemptWithMayhemEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Spell spell = game.getSpell(getTargetPointer().getFirst(game, source)); + if (spell == null) { + return false; + } + int count = 0; + for (UUID opponentId : game.getOpponents(source.getControllerId())) { + Player opponent = game.getPlayer(opponentId); + if (opponent != null && opponent.chooseUse( + Outcome.Copy, "Copy " + spell.getIdName() + '?', source, game + )) { + spell.createCopyOnStack(game, source, opponentId, true); + count++; + } + } + spell.createCopyOnStack(game, source, source.getControllerId(), true, count + 1); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TemurMonument.java b/Mage.Sets/src/mage/cards/t/TemurMonument.java index 90e414f7fd1..9507d1c5c5a 100644 --- a/Mage.Sets/src/mage/cards/t/TemurMonument.java +++ b/Mage.Sets/src/mage/cards/t/TemurMonument.java @@ -46,7 +46,7 @@ public final class TemurMonument extends CardImpl { // {3}{G}{U}{R}, {T}, Sacrifice this artifact: Create a 5/5 green Elephant creature token. Activate only as a sorcery. Ability ability = new ActivateAsSorceryActivatedAbility( - new CreateTokenEffect(new Elephant55Token(), 2), new ManaCostsImpl<>("{3}{G}{U}{R}") + new CreateTokenEffect(new Elephant55Token()), new ManaCostsImpl<>("{3}{G}{U}{R}") ); ability.addCost(new TapSourceCost()); ability.addCost(new SacrificeSourceCost()); diff --git a/Mage.Sets/src/mage/cards/t/TenaciousUnderdog.java b/Mage.Sets/src/mage/cards/t/TenaciousUnderdog.java index c5f7e9ff954..fa18c00f5fd 100644 --- a/Mage.Sets/src/mage/cards/t/TenaciousUnderdog.java +++ b/Mage.Sets/src/mage/cards/t/TenaciousUnderdog.java @@ -49,7 +49,7 @@ class TenaciousUnderdogEffect extends AsThoughEffectImpl { TenaciousUnderdogEffect() { super(AsThoughEffectType.CAST_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfGame, Outcome.PutCreatureInPlay); - staticText = "You may cast {this} from your graveyard using its blitz ability"; + staticText = "you may cast this card from your graveyard using its blitz ability"; } private TenaciousUnderdogEffect(final TenaciousUnderdogEffect effect) { diff --git a/Mage.Sets/src/mage/cards/t/TerraMagicalAdept.java b/Mage.Sets/src/mage/cards/t/TerraMagicalAdept.java new file mode 100644 index 00000000000..34864b9ec3e --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TerraMagicalAdept.java @@ -0,0 +1,60 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.ExileAndReturnSourceEffect; +import mage.abilities.effects.common.MillThenPutInHandEffect; +import mage.abilities.keyword.TransformAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.PutCards; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TerraMagicalAdept extends CardImpl { + + public TerraMagicalAdept(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WIZARD); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(4); + this.toughness = new MageInt(2); + this.secondSideCardClazz = mage.cards.e.EsperTerra.class; + + // When Terra enters, mill five cards. Put up to one enchantment milled this this way into your hand. + this.addAbility(new EntersBattlefieldTriggeredAbility(new MillThenPutInHandEffect( + 5, StaticFilters.FILTER_CARD_ENCHANTMENT, true + ))); + + // Trance -- {4}{R}{G}, {T}: Exile Terra, then return it to the battlefield transformed under its owner's control. Activate only as a sorcery. + this.addAbility(new TransformAbility()); + Ability ability = new ActivateAsSorceryActivatedAbility( + new ExileAndReturnSourceEffect(PutCards.BATTLEFIELD_TRANSFORMED), new ManaCostsImpl<>("{4}{R}{G}") + ); + ability.addCost(new TapSourceCost()); + this.addAbility(ability.withFlavorWord("Trance")); + } + + private TerraMagicalAdept(final TerraMagicalAdept card) { + super(card); + } + + @Override + public TerraMagicalAdept copy() { + return new TerraMagicalAdept(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TerritorialAetherkite.java b/Mage.Sets/src/mage/cards/t/TerritorialAetherkite.java index d2fb3427d5a..e582f734ec3 100644 --- a/Mage.Sets/src/mage/cards/t/TerritorialAetherkite.java +++ b/Mage.Sets/src/mage/cards/t/TerritorialAetherkite.java @@ -83,7 +83,7 @@ class TerritorialAetherkiteEffect extends OneShotEffect { } new GetEnergyCountersControllerEffect(2).apply(game, source); int energyToPay = controller.getAmount(0, controller.getCountersCount(CounterType.ENERGY), - "Pay any amount of {E}", game); + "Pay any amount of {E}", source, game); if (energyToPay == 0) { return true; } diff --git a/Mage.Sets/src/mage/cards/t/Tetravus.java b/Mage.Sets/src/mage/cards/t/Tetravus.java index ee741a58ccc..c7363423ee3 100644 --- a/Mage.Sets/src/mage/cards/t/Tetravus.java +++ b/Mage.Sets/src/mage/cards/t/Tetravus.java @@ -103,7 +103,7 @@ class TetravusCreateTokensEffect extends OneShotEffect { if (countersToRemove == 0) { return false; } - countersToRemove = player.getAmount(0, countersToRemove, "Choose an amount of counters to remove", game); + countersToRemove = player.getAmount(0, countersToRemove, "Choose an amount of counters to remove", source, game); Cost cost = new RemoveCountersSourceCost(CounterType.P1P1.createInstance(countersToRemove)); if (cost.pay(source, game, source, source.getControllerId(), true)) { CreateTokenEffect effect = new CreateTokenEffect(new TetraviteToken(), countersToRemove); diff --git a/Mage.Sets/src/mage/cards/t/TevalArbiterOfVirtue.java b/Mage.Sets/src/mage/cards/t/TevalArbiterOfVirtue.java index c172f35b82b..c6e156a8fde 100644 --- a/Mage.Sets/src/mage/cards/t/TevalArbiterOfVirtue.java +++ b/Mage.Sets/src/mage/cards/t/TevalArbiterOfVirtue.java @@ -1,8 +1,5 @@ package mage.cards.t; -import java.util.Objects; -import java.util.Optional; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; @@ -12,21 +9,24 @@ import mage.abilities.effects.Effect; import mage.abilities.effects.common.LoseLifeSourceControllerEffect; import mage.abilities.effects.common.continuous.GainAbilityControlledSpellsEffect; import mage.abilities.keyword.DelveAbility; -import mage.constants.SubType; -import mage.constants.SuperType; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.LifelinkAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; import mage.filter.common.FilterNonlandCard; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.AbilityPredicate; import mage.game.Game; import mage.game.stack.Spell; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; + /** - * * @author Grath */ public final class TevalArbiterOfVirtue extends CardImpl { @@ -39,7 +39,7 @@ public final class TevalArbiterOfVirtue extends CardImpl { public TevalArbiterOfVirtue(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}{G}{U}"); - + this.supertype.add(SuperType.LEGENDARY); this.subtype.add(SubType.SPIRIT); this.subtype.add(SubType.DRAGON); @@ -56,7 +56,7 @@ public final class TevalArbiterOfVirtue extends CardImpl { this.addAbility(new SimpleStaticAbility(new GainAbilityControlledSpellsEffect(new DelveAbility(false), filter))); // Whenever you cast a spell, you lose life equal to its mana value. - this.addAbility(new SpellCastControllerTriggeredAbility(new LoseLifeSourceControllerEffect(TevalArbiterOfVirtueValue.instance), false)); + this.addAbility(new SpellCastControllerTriggeredAbility(new LoseLifeSourceControllerEffect(TevalArbiterOfVirtueValue.instance).setText("you lose life equal to its mana value"), false)); } private TevalArbiterOfVirtue(final TevalArbiterOfVirtue card) { diff --git a/Mage.Sets/src/mage/cards/t/ThancredWaters.java b/Mage.Sets/src/mage/cards/t/ThancredWaters.java new file mode 100644 index 00000000000..0be306d1b80 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/ThancredWaters.java @@ -0,0 +1,71 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.FlashAbility; +import mage.abilities.keyword.IndestructibleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ThancredWaters extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledPermanent("another target legendary permanent you control"); + + static { + filter.add(AnotherPredicate.instance); + filter.add(SuperType.LEGENDARY.getPredicate()); + } + + public ThancredWaters(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(3); + this.toughness = new MageInt(5); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // Royal Guard -- When Thancred Waters enters, another target legendary permanent you control gains indestructible for as long as you control Thancred Waters. + Ability ability = new EntersBattlefieldTriggeredAbility( + new GainAbilityTargetEffect(IndestructibleAbility.getInstance(), Duration.WhileControlled) + ); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability.withFlavorWord("Royal Guard")); + + // Whenever you cast a noncreature spell, Thancred Waters gains indestructible until end of turn. + this.addAbility(new SpellCastControllerTriggeredAbility( + new GainAbilitySourceEffect(IndestructibleAbility.getInstance(), Duration.EndOfTurn), + StaticFilters.FILTER_SPELL_A_NON_CREATURE, false + )); + } + + private ThancredWaters(final ThancredWaters card) { + super(card); + } + + @Override + public ThancredWaters copy() { + return new ThancredWaters(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TheAnimus.java b/Mage.Sets/src/mage/cards/t/TheAnimus.java index 8d4fa1db8ef..74524f64cf4 100644 --- a/Mage.Sets/src/mage/cards/t/TheAnimus.java +++ b/Mage.Sets/src/mage/cards/t/TheAnimus.java @@ -1,15 +1,15 @@ package mage.cards.t; -import java.util.UUID; - import mage.abilities.Ability; import mage.abilities.common.ActivateAsSorceryActivatedAbility; -import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.CopyEffect; -import mage.cards.*; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; import mage.constants.*; import mage.counters.CounterType; import mage.filter.FilterCard; @@ -18,11 +18,14 @@ import mage.filter.common.FilterCreatureCard; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; +import mage.target.TargetPermanent; import mage.target.Targets; -import mage.target.common.*; +import mage.target.common.TargetCardInExile; +import mage.target.common.TargetCardInGraveyard; + +import java.util.UUID; /** - * * @author grimreap124 */ public final class TheAnimus extends CardImpl { @@ -37,7 +40,7 @@ public final class TheAnimus extends CardImpl { public TheAnimus(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); - + this.supertype.add(SuperType.LEGENDARY); // At the beginning of your end step, exile up to one target legendary creature card from a graveyard with a memory counter on it. @@ -47,7 +50,7 @@ public final class TheAnimus extends CardImpl { // {T}: Until your next turn, target legendary creature you control becomes a copy of target creature card in exile with a memory counter on it. Activate only as a sorcery. ability = new ActivateAsSorceryActivatedAbility(new TheAnimusCopyEffect(), new TapSourceCost()); - ability.addTarget(new TargetCreaturePermanentSameController(1, StaticFilters.FILTER_CREATURE_LEGENDARY)); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CONTROLLED_CREATURE_LEGENDARY)); ability.addTarget(new TargetCardInExile(1, 1, exileFilter)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/t/TheCavesOfAndrozani.java b/Mage.Sets/src/mage/cards/t/TheCavesOfAndrozani.java new file mode 100644 index 00000000000..cc0ed75e384 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheCavesOfAndrozani.java @@ -0,0 +1,149 @@ +package mage.cards.t; + +import mage.abilities.Ability; +import mage.abilities.common.SagaAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.choices.Choice; +import mage.choices.ChoiceImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SagaChapter; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.FilterCard; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.permanent.CounterAnyPredicate; +import mage.filter.predicate.permanent.TappedPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.common.TargetCardInLibrary; +import mage.util.RandomUtil; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author TheElk801 + */ +public final class TheCavesOfAndrozani extends CardImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent("tapped creatures"); + private static final FilterCard filter2 = new FilterCard("a Doctor card"); + + static { + filter.add(TappedPredicate.TAPPED); + filter2.add(SubType.DOCTOR.getPredicate()); + } + + public TheCavesOfAndrozani(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{W}"); + + this.subtype.add(SubType.SAGA); + + // (As this Saga enters and after your draw step, add a lore counter. Sacrifice after IV.) + SagaAbility sagaAbility = new SagaAbility(this, SagaChapter.CHAPTER_IV); + + // I -- Put two stun counters on each of up to two target tapped creatures. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_I, + new AddCountersTargetEffect(CounterType.STUN.createInstance(2)), + new TargetPermanent(0, 2, filter) + ); + + // II, III -- For each non-Saga permanent, choose a counter on it. You may put an additional counter of that kind on that permanent. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_II, SagaChapter.CHAPTER_III, + new TheCavesOfAndrozaniEffect() + ); + + // IV -- Search your library for a Doctor card, reveal it, put it into your hand, then shuffle. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_IV, + new SearchLibraryPutInHandEffect(new TargetCardInLibrary(filter2), true) + ); + this.addAbility(sagaAbility); + } + + private TheCavesOfAndrozani(final TheCavesOfAndrozani card) { + super(card); + } + + @Override + public TheCavesOfAndrozani copy() { + return new TheCavesOfAndrozani(this); + } +} + +class TheCavesOfAndrozaniEffect extends OneShotEffect { + + private static final FilterPermanent filter = new FilterPermanent("non-Saga permanent"); + + static { + filter.add(Predicates.not(SubType.SAGA.getPredicate())); + filter.add(CounterAnyPredicate.instance); + } + + TheCavesOfAndrozaniEffect() { + super(Outcome.Benefit); + staticText = "for each non-Saga permanent, choose a counter on it. " + + "You may put an additional counter of that kind on that permanent"; + } + + private TheCavesOfAndrozaniEffect(final TheCavesOfAndrozaniEffect effect) { + super(effect); + } + + @Override + public TheCavesOfAndrozaniEffect copy() { + return new TheCavesOfAndrozaniEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + TargetPermanent target = new TargetPermanent(0, Integer.MAX_VALUE, filter, true); + target.withChooseHint("to add another counter to"); + player.choose(outcome, target, source, game); + List permanents = target + .getTargets() + .stream() + .map(game::getPermanent) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + if (permanents.isEmpty()) { + return false; + } + for (Permanent permanent : permanents) { + Set counterTypes = new HashSet<>(permanent.getCounters(game).keySet()); + CounterType counterType; + switch (counterTypes.size()) { + case 0: + continue; + case 1: + counterType = CounterType.findByName(RandomUtil.randomFromCollection(counterTypes)); + break; + default: + Choice choice = new ChoiceImpl(true); + choice.setMessage("Choose a type of counter to add to " + permanent.getIdName()); + choice.setChoices(counterTypes); + player.choose(outcome, choice, game); + counterType = CounterType.findByName(choice.getChoice()); + } + if (counterType != null) { + permanent.addCounters(counterType.createInstance(), source, game); + } + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TheCircleOfLoyalty.java b/Mage.Sets/src/mage/cards/t/TheCircleOfLoyalty.java index cbdb6fd20a4..a8b6ed085b2 100644 --- a/Mage.Sets/src/mage/cards/t/TheCircleOfLoyalty.java +++ b/Mage.Sets/src/mage/cards/t/TheCircleOfLoyalty.java @@ -6,11 +6,11 @@ import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SpellCastControllerTriggeredAbility; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.AffinityEffect; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.continuous.BoostControlledEffect; -import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; +import mage.abilities.hint.Hint; import mage.abilities.hint.ValueHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -26,17 +26,14 @@ import java.util.UUID; */ public final class TheCircleOfLoyalty extends CardImpl { + private static final FilterControlledPermanent filterKnight = new FilterControlledPermanent(SubType.KNIGHT, "Knights"); private static final FilterSpell filterLegendary = new FilterSpell("a legendary spell"); static { filterLegendary.add(SuperType.LEGENDARY.getPredicate()); } - static final FilterControlledPermanent filterKnight = new FilterControlledPermanent("Knight you control"); - - static { - filterKnight.add(SubType.KNIGHT.getPredicate()); - } + private static final Hint hint = new ValueHint("Knights you control", new PermanentsOnBattlefieldCount(filterKnight)); public TheCircleOfLoyalty(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{4}{W}{W}"); @@ -44,10 +41,7 @@ public final class TheCircleOfLoyalty extends CardImpl { this.supertype.add(SuperType.LEGENDARY); // This spell costs {1} less to cast for each Knight you control. - DynamicValue xValue = new PermanentsOnBattlefieldCount(filterKnight); - this.addAbility(new SimpleStaticAbility( - Zone.ALL, new SpellCostReductionForEachSourceEffect(1, xValue) - ).addHint(new ValueHint("Knight you control", xValue))); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new AffinityEffect(filterKnight)).addHint(hint)); // Creatures you control get +1/+1. this.addAbility(new SimpleStaticAbility( @@ -75,4 +69,4 @@ public final class TheCircleOfLoyalty extends CardImpl { public TheCircleOfLoyalty copy() { return new TheCircleOfLoyalty(this); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/t/TheCrystalsChosen.java b/Mage.Sets/src/mage/cards/t/TheCrystalsChosen.java new file mode 100644 index 00000000000..3c834351301 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheCrystalsChosen.java @@ -0,0 +1,37 @@ +package mage.cards.t; + +import mage.abilities.effects.common.CreateTokenEffect; +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 mage.game.permanent.token.HeroToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TheCrystalsChosen extends CardImpl { + + public TheCrystalsChosen(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{5}{W}{W}"); + + // Create four 1/1 colorless Hero creature tokens. Then put a +1/+1 counter on each creature you control. + this.getSpellAbility().addEffect(new CreateTokenEffect(new HeroToken(), 4)); + this.getSpellAbility().addEffect(new AddCountersAllEffect( + CounterType.P1P1.createInstance(), StaticFilters.FILTER_CONTROLLED_CREATURE + ).concatBy("Then")); + } + + private TheCrystalsChosen(final TheCrystalsChosen card) { + super(card); + } + + @Override + public TheCrystalsChosen copy() { + return new TheCrystalsChosen(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TheDalekEmperor.java b/Mage.Sets/src/mage/cards/t/TheDalekEmperor.java index 9aebe165240..a60b3904f57 100644 --- a/Mage.Sets/src/mage/cards/t/TheDalekEmperor.java +++ b/Mage.Sets/src/mage/cards/t/TheDalekEmperor.java @@ -2,15 +2,15 @@ package mage.cards.t; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; -import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.AffinityEffect; +import mage.abilities.effects.common.FaceVillainousChoiceOpponentsEffect; import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; import mage.abilities.hint.Hint; import mage.abilities.hint.ValueHint; import mage.abilities.keyword.HasteAbility; +import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.choices.FaceVillainousChoice; @@ -33,6 +33,9 @@ public final class TheDalekEmperor extends CardImpl { private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.DALEK, "Daleks"); private static final Hint hint = new ValueHint("Daleks you control", new PermanentsOnBattlefieldCount(filter)); + private static final FaceVillainousChoice choice = new FaceVillainousChoice( + Outcome.Sacrifice, new TheDalekEmperorFirstChoice(), new TheDalekEmperorSecondChoice() + ); public TheDalekEmperor(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{5}{B}{R}"); @@ -51,9 +54,7 @@ public final class TheDalekEmperor extends CardImpl { ))); // At the beginning of combat on your turn, each opponent faces a villainous choice -- That player sacrifices a creature they control, or you create a 3/3 black Dalek artifact creature token with menace. - this.addAbility(new BeginningOfCombatTriggeredAbility( - new TheDalekEmperorEffect() - )); + this.addAbility(new BeginningOfCombatTriggeredAbility(new FaceVillainousChoiceOpponentsEffect(choice))); } private TheDalekEmperor(final TheDalekEmperor card) { @@ -66,40 +67,10 @@ public final class TheDalekEmperor extends CardImpl { } } -class TheDalekEmperorEffect extends OneShotEffect { - - private static final FaceVillainousChoice choice = new FaceVillainousChoice( - Outcome.Sacrifice, new TheDalekEmperorFirstChoice(), new TheDalekEmperorSecondChoice() - ); - - TheDalekEmperorEffect() { - super(Outcome.Benefit); - staticText = "each opponent " + choice.generateRule(); - } - - private TheDalekEmperorEffect(final TheDalekEmperorEffect effect) { - super(effect); - } - - @Override - public TheDalekEmperorEffect copy() { - return new TheDalekEmperorEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - for (UUID playerId : game.getOpponents(source.getControllerId())) { - Player player = game.getPlayer(playerId); - choice.faceChoice(player, game, source); - } - return true; - } -} - class TheDalekEmperorFirstChoice extends VillainousChoice { TheDalekEmperorFirstChoice() { - super("That player sacrifices a creature they control", "You sacrifice a creature"); + super("That player sacrifices a creature of their choice", "You sacrifice a creature"); } @Override diff --git a/Mage.Sets/src/mage/cards/t/TheDayOfTheDoctor.java b/Mage.Sets/src/mage/cards/t/TheDayOfTheDoctor.java new file mode 100644 index 00000000000..27e978726b8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheDayOfTheDoctor.java @@ -0,0 +1,140 @@ +package mage.cards.t; + +import mage.abilities.Ability; +import mage.abilities.common.SagaAbility; +import mage.abilities.effects.OneShotEffect; +import mage.cards.*; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.util.CardUtil; + +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * @author TheElk801 + */ +public final class TheDayOfTheDoctor extends CardImpl { + + public TheDayOfTheDoctor(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{R}{W}"); + + this.subtype.add(SubType.SAGA); + + // (As this Saga enters and after your draw step, add a lore counter. Sacrifice after IV.) + SagaAbility sagaAbility = new SagaAbility(this, SagaChapter.CHAPTER_IV); + + // I, II, III -- Exile cards from the top of your library until you exile a legendary card. You may play that card for as long as The Day of the Doctor remains on the battlefield. Put the rest of those exiled cards on the bottom of your library in a random order. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_I, SagaChapter.CHAPTER_III, new TheDayOfTheDoctorExileEffect()); + + // IV -- Choose up to three Doctors. You may exile all other creatures. If you do, The Day of the Doctor deals 13 damage to you. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_IV, new TheDayOfTheDoctorChooseEffect()); + this.addAbility(sagaAbility); + } + + private TheDayOfTheDoctor(final TheDayOfTheDoctor card) { + super(card); + } + + @Override + public TheDayOfTheDoctor copy() { + return new TheDayOfTheDoctor(this); + } +} + +class TheDayOfTheDoctorExileEffect extends OneShotEffect { + + TheDayOfTheDoctorExileEffect() { + super(Outcome.Benefit); + staticText = "exile cards from the top of your library until you exile a legendary card. " + + "You may play that card for as long as {this} remains on the battlefield. " + + "Put the rest of those exiled cards on the bottom of your library in a random order"; + } + + private TheDayOfTheDoctorExileEffect(final TheDayOfTheDoctorExileEffect effect) { + super(effect); + } + + @Override + public TheDayOfTheDoctorExileEffect copy() { + return new TheDayOfTheDoctorExileEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Cards cards = new CardsImpl(); + Card card = getCard(player, cards, source, game); + CardUtil.makeCardPlayable( + game, source, card, false, + Duration.UntilSourceLeavesBattlefield, false + ); + player.putCardsOnBottomOfLibrary(cards, game, source, false); + return true; + } + + private static Card getCard(Player player, Cards cards, Ability source, Game game) { + for (Card card : player.getLibrary().getCards(game)) { + player.moveCards(card, Zone.EXILED, source, game); + if (card.isLegendary(game)) { + return card; + } + cards.add(card); + } + return null; + } +} + +class TheDayOfTheDoctorChooseEffect extends OneShotEffect { + + private static final FilterPermanent filter = new FilterPermanent(SubType.DOCTOR, "Doctors"); + + TheDayOfTheDoctorChooseEffect() { + super(Outcome.Benefit); + staticText = "choose up to three Doctors. You may exile all other creatures. " + + "If you do, {this} deals 13 damage to you"; + } + + private TheDayOfTheDoctorChooseEffect(final TheDayOfTheDoctorChooseEffect effect) { + super(effect); + } + + @Override + public TheDayOfTheDoctorChooseEffect copy() { + return new TheDayOfTheDoctorChooseEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + TargetPermanent target = new TargetPermanent(0, 3, filter, true); + player.choose(outcome, target, source, game); + if (!player.chooseUse(outcome, "Exile all other creatures?", source, game)) { + return false; + } + player.moveCards( + game.getBattlefield() + .getActivePermanents( + StaticFilters.FILTER_PERMANENT_CREATURE, + source.getSourceId(), source, game + ) + .stream() + .filter(permanent -> !target.getTargets() + .contains(permanent.getId())) + .collect(Collectors.toSet()), + Zone.EXILED, source, game + ); + player.damage(13, source, game); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TheEleventhDoctor.java b/Mage.Sets/src/mage/cards/t/TheEleventhDoctor.java new file mode 100644 index 00000000000..5580d833394 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheEleventhDoctor.java @@ -0,0 +1,99 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.combat.CantBeBlockedTargetEffect; +import mage.abilities.keyword.SuspendAbility; +import mage.cards.Card; +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.mageobject.PowerPredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.TargetPermanent; +import mage.target.common.TargetCardInHand; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TheEleventhDoctor extends CardImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent("creature with power 3 or less"); + + static { + filter.add(new PowerPredicate(ComparisonType.FEWER_THAN, 4)); + } + + public TheEleventhDoctor(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.TIME_LORD); + this.subtype.add(SubType.DOCTOR); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // I. AM. TALKING! -- Whenever The Eleventh Doctor deals combat damage to a player, you may exile a card from your hand with a number of time counters on it equal to its mana value. If it doesn't have suspend, it gains suspend. + this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility(new TheEleventhDoctorEffect()).withFlavorWord("I. AM. TALKING!")); + + // {2}: Target creature with power 3 or less can't be blocked this turn. + Ability ability = new SimpleActivatedAbility(new CantBeBlockedTargetEffect(Duration.EndOfTurn), new GenericManaCost(2)); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + } + + private TheEleventhDoctor(final TheEleventhDoctor card) { + super(card); + } + + @Override + public TheEleventhDoctor copy() { + return new TheEleventhDoctor(this); + } +} + +class TheEleventhDoctorEffect extends OneShotEffect { + + TheEleventhDoctorEffect() { + super(Outcome.Benefit); + staticText = "you may exile a card from your hand with a number of time counters on it " + + "equal to its mana value. If it doesn't have suspend, it gains suspend"; + } + + private TheEleventhDoctorEffect(final TheEleventhDoctorEffect effect) { + super(effect); + } + + @Override + public TheEleventhDoctorEffect copy() { + return new TheEleventhDoctorEffect(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); + player.choose(Outcome.PlayForFree, player.getHand(), target, source, game); + Card card = game.getCard(target.getFirstTarget()); + if (card == null) { + return false; + } + player.moveCards(card, Zone.EXILED, source, game); + SuspendAbility.addTimeCountersAndSuspend(card, card.getManaValue(), source, game); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TheEmperorOfPalamecia.java b/Mage.Sets/src/mage/cards/t/TheEmperorOfPalamecia.java new file mode 100644 index 00000000000..734c875717f --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheEmperorOfPalamecia.java @@ -0,0 +1,72 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.SourceHasCounterCondition; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.TransformAbility; +import mage.abilities.mana.ConditionalColoredManaAbility; +import mage.abilities.mana.builder.ConditionalManaBuilder; +import mage.abilities.mana.conditional.ConditionalSpellManaBuilder; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TheEmperorOfPalamecia extends CardImpl { + + private final ConditionalManaBuilder manaBuilder + = new ConditionalSpellManaBuilder(StaticFilters.FILTER_SPELLS_NON_CREATURE); + private static final Condition condition = new SourceHasCounterCondition(CounterType.P1P1, 3); + + public TheEmperorOfPalamecia(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{U}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.NOBLE); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + this.secondSideCardClazz = mage.cards.t.TheLordMasterOfHell.class; + + // {T}: Add {U} or {R}. Spend this mana only to cast a noncreature spell. + this.addAbility(new ConditionalColoredManaAbility(new TapSourceCost(), Mana.BlueMana(1), manaBuilder)); + this.addAbility(new ConditionalColoredManaAbility(new TapSourceCost(), Mana.RedMana(1), manaBuilder)); + + // Whenever you cast a noncreature spell, if at least four mana was spent to cast it, put a +1/+1 counter on The Emperor of Palamecia. Then if it has three or more +1/+1 counters on it, transform it. + this.addAbility(new TransformAbility()); + Ability ability = new SpellCastControllerTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), + StaticFilters.FILTER_NONCREATURE_SPELL_FOUR_MANA_SPENT, false + ); + ability.addEffect(new ConditionalOneShotEffect( + new TransformSourceEffect(), condition, + "Then if it has three or more +1/+1 counters on it, transform it" + )); + this.addAbility(ability); + } + + private TheEmperorOfPalamecia(final TheEmperorOfPalamecia card) { + super(card); + } + + @Override + public TheEmperorOfPalamecia copy() { + return new TheEmperorOfPalamecia(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TheFaceOfBoe.java b/Mage.Sets/src/mage/cards/t/TheFaceOfBoe.java new file mode 100644 index 00000000000..146d1c02195 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheFaceOfBoe.java @@ -0,0 +1,99 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.AbilityImpl; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.SuspendAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterCard; +import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInHand; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TheFaceOfBoe extends CardImpl { + + public TheFaceOfBoe(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}{R}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.ALIEN); + this.subtype.add(SubType.ADVISOR); + this.power = new MageInt(0); + this.toughness = new MageInt(4); + + // {T}: You may cast a spell with suspend from your hand. If you do, pay its suspend cost rather than its mana cost. Activate only as a sorcery. + this.addAbility(new ActivateAsSorceryActivatedAbility(new TheFaceOfBoeEffect(), new TapSourceCost())); + } + + private TheFaceOfBoe(final TheFaceOfBoe card) { + super(card); + } + + @Override + public TheFaceOfBoe copy() { + return new TheFaceOfBoe(this); + } +} + +class TheFaceOfBoeEffect extends OneShotEffect { + + private static final FilterCard filter = new FilterCard("card with suspend"); + + static { + filter.add(new AbilityPredicate(SuspendAbility.class)); + } + + TheFaceOfBoeEffect() { + super(Outcome.Benefit); + staticText = "you may cast a spell with suspend from your hand. " + + "If you do, pay its suspend cost rather than its mana cost"; + } + + private TheFaceOfBoeEffect(final TheFaceOfBoeEffect effect) { + super(effect); + } + + @Override + public TheFaceOfBoeEffect copy() { + return new TheFaceOfBoeEffect(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, filter); + player.choose(outcome, player.getHand(), target, source, game); + Card card = game.getCard(target.getFirstTarget()); + if (card == null) { + return false; + } + ManaCostsImpl cost = new ManaCostsImpl<>(); + CardUtil.castStream(card.getAbilities(game), SuspendAbility.class) + .map(AbilityImpl::getManaCosts) + .forEach(cost::addAll); + CardUtil.castSingle(player, source, game, card, cost); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TheFalconAirshipRestored.java b/Mage.Sets/src/mage/cards/t/TheFalconAirshipRestored.java new file mode 100644 index 00000000000..2aabc29882f --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheFalconAirshipRestored.java @@ -0,0 +1,67 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DoWhenCostPaid; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.abilities.effects.common.ReturnSourceFromGraveyardToBattlefieldEffect; +import mage.abilities.keyword.CrewAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TheFalconAirshipRestored extends CardImpl { + + public TheFalconAirshipRestored(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.VEHICLE); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever The Falcon deals combat damage to a player, you may sacrifice it. When you do, return target creature card from your graveyard to the battlefield. + ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility( + new ReturnFromGraveyardToBattlefieldTargetEffect(), false + ); + ability.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_CREATURE_YOUR_GRAVEYARD)); + this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility(new DoWhenCostPaid( + ability, new SacrificeSourceCost(), "Sacrifice this creature?" + ))); + + // {4}{B}: Return this card from your graveyard to the battlefield tapped. + this.addAbility(new SimpleActivatedAbility( + Zone.GRAVEYARD, new ReturnSourceFromGraveyardToBattlefieldEffect(true), new ManaCostsImpl<>("{4}{B}") + )); + + // Crew 2 + this.addAbility(new CrewAbility(2)); + } + + private TheFalconAirshipRestored(final TheFalconAirshipRestored card) { + super(card); + } + + @Override + public TheFalconAirshipRestored copy() { + return new TheFalconAirshipRestored(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TheFifthDoctor.java b/Mage.Sets/src/mage/cards/t/TheFifthDoctor.java index fcf5fad195e..c92bc62a4f5 100644 --- a/Mage.Sets/src/mage/cards/t/TheFifthDoctor.java +++ b/Mage.Sets/src/mage/cards/t/TheFifthDoctor.java @@ -28,7 +28,7 @@ import java.util.UUID; public final class TheFifthDoctor extends CardImpl { private static final FilterPermanent filter = new FilterControlledCreaturePermanent( - "creature you control that didn't attack or enter the battlefield this turn" + "creature you control that didn't attack or enter this turn" ); static { diff --git a/Mage.Sets/src/mage/cards/t/TheLordMasterOfHell.java b/Mage.Sets/src/mage/cards/t/TheLordMasterOfHell.java new file mode 100644 index 00000000000..86f366c3704 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheLordMasterOfHell.java @@ -0,0 +1,65 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; +import mage.abilities.effects.common.DamagePlayersEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.TargetController; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicates; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TheLordMasterOfHell extends CardImpl { + + private static final FilterCard filter = new FilterCard("noncreature, nonland cards in your graveyard"); + + static { + filter.add(Predicates.not(CardType.CREATURE.getPredicate())); + filter.add(Predicates.not(CardType.LAND.getPredicate())); + } + + private static final DynamicValue xValue = new CardsInControllerGraveyardCount(filter); + private static final Hint hint = new ValueHint("Noncreature, nonland cards in your graveyard", xValue); + + public TheLordMasterOfHell(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.DEMON); + this.subtype.add(SubType.NOBLE); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + this.nightCard = true; + this.color.setBlue(true); + this.color.setRed(true); + + // Starfall -- Whenever The Lord Master of Hell attacks, it deals X damage to each opponent, where X is the number of noncreature, nonland cards in your graveyard. + this.addAbility(new AttacksTriggeredAbility(new DamagePlayersEffect( + xValue, TargetController.OPPONENT + ).setText("it deals X damage to each opponent, where X is " + + "the number of noncreature, nonland cards in your graveyard")) + .withFlavorWord("Starfall").addHint(hint)); + } + + private TheLordMasterOfHell(final TheLordMasterOfHell card) { + super(card); + } + + @Override + public TheLordMasterOfHell copy() { + return new TheLordMasterOfHell(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TheMasterFormedAnew.java b/Mage.Sets/src/mage/cards/t/TheMasterFormedAnew.java new file mode 100644 index 00000000000..49ec173d33a --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheMasterFormedAnew.java @@ -0,0 +1,134 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CastSourceTriggeredAbility; +import mage.abilities.effects.common.CopyEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; +import mage.filter.FilterCard; +import mage.filter.common.FilterCreatureCard; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.TargetPermanent; +import mage.target.common.TargetCardInExile; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TheMasterFormedAnew extends CardImpl { + + public TheMasterFormedAnew(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{U}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.TIME_LORD); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(0); + this.toughness = new MageInt(1); + + // Body Thief -- When you cast this spell, you may exile a creature you control and put a takeover counter on it. + this.addAbility(new CastSourceTriggeredAbility(new TheMasterFormedAnewExileEffect()).withFlavorWord("Body Thief")); + + // You may have The Master, Formed Anew enter the battlefield as a copy of a creature card in exile with a takeover counter on it. + this.addAbility(new EntersBattlefieldAbility(new TheMasterFormedAnewCopyEffect(), true)); + } + + private TheMasterFormedAnew(final TheMasterFormedAnew card) { + super(card); + } + + @Override + public TheMasterFormedAnew copy() { + return new TheMasterFormedAnew(this); + } +} + +class TheMasterFormedAnewExileEffect extends OneShotEffect { + + TheMasterFormedAnewExileEffect() { + super(Outcome.Benefit); + staticText = "you may exile a creature you control and put a takeover counter on it"; + } + + private TheMasterFormedAnewExileEffect(final TheMasterFormedAnewExileEffect effect) { + super(effect); + } + + @Override + public TheMasterFormedAnewExileEffect copy() { + return new TheMasterFormedAnewExileEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + TargetPermanent target = new TargetControlledCreaturePermanent(0, 1); + target.withNotTarget(true); + player.choose(outcome, target, source, game); + Permanent permanent = game.getPermanent(target.getFirstTarget()); + if (permanent == null) { + return false; + } + Card card = permanent.getMainCard(); + player.moveCards(permanent, Zone.EXILED, source, game); + if (card != null && Zone.EXILED.match(game.getState().getZone(card.getId()))) { + card.addCounters(CounterType.TAKEOVER.createInstance(), source, game); + } + return true; + } +} + +class TheMasterFormedAnewCopyEffect extends OneShotEffect { + + private static final FilterCard filter + = new FilterCreatureCard("a creature card in exile with a takeover counter on it"); + + static { + filter.add(CounterType.TAKEOVER.getPredicate()); + } + + TheMasterFormedAnewCopyEffect() { + super(Outcome.Benefit); + staticText = "as a copy of a creature card in exile with a takeover counter on it"; + } + + private TheMasterFormedAnewCopyEffect(final TheMasterFormedAnewCopyEffect effect) { + super(effect); + } + + @Override + public TheMasterFormedAnewCopyEffect copy() { + return new TheMasterFormedAnewCopyEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + TargetCard target = new TargetCardInExile(0, 1, filter); + target.withNotTarget(true); + player.choose(outcome, target, source, game); + Card card = game.getCard(target.getFirstTarget()); + if (card == null) { + return false; + } + game.addEffect(new CopyEffect(Duration.Custom, card, source.getSourceId()), source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TheMasterGallifreysEnd.java b/Mage.Sets/src/mage/cards/t/TheMasterGallifreysEnd.java new file mode 100644 index 00000000000..2c22b7acd0a --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheMasterGallifreysEnd.java @@ -0,0 +1,157 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DiesCreatureTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenCopyTargetEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.choices.FaceVillainousChoice; +import mage.choices.VillainousChoice; +import mage.constants.*; +import mage.filter.FilterOpponent; +import mage.filter.FilterPermanent; +import mage.filter.FilterPlayer; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetPlayer; +import mage.target.targetpointer.FixedTarget; + +import java.util.Objects; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TheMasterGallifreysEnd extends CardImpl { + + private static final FilterPermanent filter + = new FilterControlledCreaturePermanent("a nontoken artifact creature you control"); + + static { + filter.add(CardType.ARTIFACT.getPredicate()); + filter.add(TokenPredicate.FALSE); + } + + public TheMasterGallifreysEnd(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.TIME_LORD); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // Make Them Pay -- Whenever a nontoken artifact creature you control dies, you may exile it. If you do, choose an opponent with the most life among your opponents. That player faces a villainous choice -- They lose 4 life, or you create a token that's a copy of that card. + this.addAbility(new DiesCreatureTriggeredAbility( + new TheMasterGallifreysEndEffect(), true, filter, true + ).withFlavorWord("Make Them Pay")); + } + + private TheMasterGallifreysEnd(final TheMasterGallifreysEnd card) { + super(card); + } + + @Override + public TheMasterGallifreysEnd copy() { + return new TheMasterGallifreysEnd(this); + } +} + +enum TheMasterGallifreysEndPredicate implements ObjectSourcePlayerPredicate { + instance; + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + return input + .getObject() + .getLife() + >= game + .getOpponents(input.getPlayerId()) + .stream() + .map(game::getPlayer) + .filter(Objects::nonNull) + .mapToInt(Player::getLife) + .max() + .orElse(Integer.MIN_VALUE); + } +} + +class TheMasterGallifreysEndEffect extends OneShotEffect { + + private static final FilterPlayer filter = new FilterOpponent("opponent with the most life among your opponents"); + + static { + filter.add(TheMasterGallifreysEndPredicate.instance); + } + + TheMasterGallifreysEndEffect() { + super(Outcome.Benefit); + staticText = "exile it. If you do, choose an opponent with " + + "the most life among your opponents. That player faces a villainous choice " + + "— They lose 4 life, or you create a token that's a copy of that card"; + } + + private TheMasterGallifreysEndEffect(final TheMasterGallifreysEndEffect effect) { + super(effect); + } + + @Override + public TheMasterGallifreysEndEffect copy() { + return new TheMasterGallifreysEndEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); + if (player == null || card == null) { + return false; + } + player.moveCards(card, Zone.EXILED, source, game); + TargetPlayer target = new TargetPlayer(filter); + target.withNotTarget(true); + player.chooseTarget(outcome, target, source, game); + Player opponent = game.getPlayer(target.getFirstTarget()); + return opponent != null && new FaceVillainousChoice( + Outcome.LoseLife, + new TheMasterGallifreysEndFirstChoice(), + new TheMasterGallifreysEndSecondChoice(card) + ).faceChoice(opponent, game, source); + } +} + +class TheMasterGallifreysEndFirstChoice extends VillainousChoice { + + TheMasterGallifreysEndFirstChoice() { + super("", "Lose 4 life"); + } + + @Override + public boolean doChoice(Player player, Game game, Ability source) { + return player.loseLife(4, game, source, false) > 0; + } +} + +class TheMasterGallifreysEndSecondChoice extends VillainousChoice { + + private final Card card; + + TheMasterGallifreysEndSecondChoice(Card card) { + super("", "{controller} creates a token that's a copy of the exiled card"); + this.card = card; + } + + @Override + public boolean doChoice(Player player, Game game, Ability source) { + return new CreateTokenCopyTargetEffect() + .setTargetPointer(new FixedTarget(card, game)) + .apply(game, source); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TheMasterMesmerist.java b/Mage.Sets/src/mage/cards/t/TheMasterMesmerist.java new file mode 100644 index 00000000000..f5f8e9bb77b --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheMasterMesmerist.java @@ -0,0 +1,90 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.DealsDamageToAPlayerAllTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.combat.GoadTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.SkulkAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.common.FilterOpponentsCreaturePermanent; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TheMasterMesmerist extends CardImpl { + + private static final FilterPermanent filter = new FilterOpponentsCreaturePermanent("creature an opponent controls with power less than or equal to {this}'s power"); + private static final FilterPermanent filter2 = new FilterCreaturePermanent(); + + static { + filter.add(TheMasterMesmeristPredicate.instance); + filter2.add(new AbilityPredicate(SkulkAbility.class)); + } + + public TheMasterMesmerist(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.TIME_LORD); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // {T}: Target creature an opponent controls with power less than or equal to The Master's power gains skulk until end of turn. Goad it. + Ability ability = new SimpleActivatedAbility(new GainAbilityTargetEffect(new SkulkAbility()), new TapSourceCost()); + ability.addEffect(new GoadTargetEffect().setText("goad it")); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + + // Whenever a creature with skulk deals combat damage to one of your opponents, put a +1/+1 counter on The Master and draw a card. + ability = new DealsDamageToAPlayerAllTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), filter2, false, + SetTargetPointer.NONE, true, false, TargetController.OPPONENT + ).setTriggerPhrase("Whenever a creature with skulk deals combat damage to one of your opponents, "); + ability.addEffect(new DrawCardSourceControllerEffect(1).concatBy("and")); + this.addAbility(ability); + } + + private TheMasterMesmerist(final TheMasterMesmerist card) { + super(card); + } + + @Override + public TheMasterMesmerist copy() { + return new TheMasterMesmerist(this); + } +} + +enum TheMasterMesmeristPredicate implements ObjectSourcePlayerPredicate { + instance; + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + return Optional + .ofNullable(input.getSource().getSourcePermanentOrLKI(game)) + .map(MageObject::getPower) + .map(MageInt::getValue) + .map(p -> input.getObject().getPower().getValue() <= p) + .orElse(false); + } +} diff --git a/Mage.Sets/src/mage/cards/t/ThePartingOfTheWays.java b/Mage.Sets/src/mage/cards/t/ThePartingOfTheWays.java new file mode 100644 index 00000000000..c65cefa54da --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/ThePartingOfTheWays.java @@ -0,0 +1,98 @@ +package mage.cards.t; + +import mage.abilities.Ability; +import mage.abilities.common.SagaAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.effects.common.counter.TimeTravelEffect; +import mage.abilities.keyword.SuspendAbility; +import mage.cards.*; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetArtifactPermanent; +import mage.target.targetadjustment.ForEachOpponentTargetsAdjuster; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ThePartingOfTheWays extends CardImpl { + + public ThePartingOfTheWays(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{4}{R}{R}"); + + this.subtype.add(SubType.SAGA); + + // (As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.) + SagaAbility sagaAbility = new SagaAbility(this); + + // I -- Exile the top five cards of your library. For each nonland card exiled this way, put a number of time counters on that card equal to its mana value. If it doesn't have suspend, it gains suspend. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_I, new ThePartingOfTheWaysEffect()); + + // II -- Time travel, then time travel. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_II, + new TimeTravelEffect(false), + new TimeTravelEffect(false) + .concatBy(", then") + ); + + // III -- For each opponent, destroy up to one target artifact that player controls. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_III, + ability -> { + ability.addEffect(new DestroyTargetEffect() + .setText("For each opponent, destroy up to one target artifact that player controls")); + ability.addTarget(new TargetArtifactPermanent(0, 1)); + ability.setTargetAdjuster(new ForEachOpponentTargetsAdjuster()); + } + ); + this.addAbility(sagaAbility); + } + + private ThePartingOfTheWays(final ThePartingOfTheWays card) { + super(card); + } + + @Override + public ThePartingOfTheWays copy() { + return new ThePartingOfTheWays(this); + } +} + +class ThePartingOfTheWaysEffect extends OneShotEffect { + + ThePartingOfTheWaysEffect() { + super(Outcome.Benefit); + staticText = "exile the top five cards of your library. For each nonland card exiled this way, " + + "put a number of time counters on that card equal to its mana value. " + + "If it doesn't have suspend, it gains suspend"; + } + + private ThePartingOfTheWaysEffect(final ThePartingOfTheWaysEffect effect) { + super(effect); + } + + @Override + public ThePartingOfTheWaysEffect copy() { + return new ThePartingOfTheWaysEffect(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, 5)); + player.moveCards(cards, Zone.EXILED, source, game); + cards.retainZone(Zone.EXILED, game); + for (Card card : cards.getCards(StaticFilters.FILTER_CARD_NON_LAND, game)) { + SuspendAbility.addTimeCountersAndSuspend(card, card.getManaValue(), source, game); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/t/ThePrimaVista.java b/Mage.Sets/src/mage/cards/t/ThePrimaVista.java new file mode 100644 index 00000000000..57dac8030c3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/ThePrimaVista.java @@ -0,0 +1,52 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.common.continuous.AddCardTypeSourceEffect; +import mage.abilities.keyword.CrewAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ThePrimaVista extends CardImpl { + + public ThePrimaVista(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{4}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.VEHICLE); + this.power = new MageInt(5); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever you cast a noncreature spell, if at least four mana was spent to cast it, The Prima Vista becomes an artifact creature until end of turn. + this.addAbility(new SpellCastControllerTriggeredAbility( + new AddCardTypeSourceEffect(Duration.EndOfTurn, CardType.ARTIFACT, CardType.CREATURE), + StaticFilters.FILTER_NONCREATURE_SPELL_FOUR_MANA_SPENT, false + )); + + // Crew 2 + this.addAbility(new CrewAbility(2)); + } + + private ThePrimaVista(final ThePrimaVista card) { + super(card); + } + + @Override + public ThePrimaVista copy() { + return new ThePrimaVista(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TheRedTerror.java b/Mage.Sets/src/mage/cards/t/TheRedTerror.java new file mode 100644 index 00000000000..96960a7b93b --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheRedTerror.java @@ -0,0 +1,93 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.BatchTriggeredAbility; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.game.Controllable; +import mage.game.Game; +import mage.game.events.DamagedBatchBySourceEvent; +import mage.game.events.DamagedEvent; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class TheRedTerror extends CardImpl { + + public TheRedTerror(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.TYRANID); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // Advanced Species -- Whenever a red source you control deals damage to one or more permanents and/or players, put a +1/+1 counter on The Red Terror. + this.addAbility(new TheRedTerrorTrigger().withFlavorWord("Advanced Species")); + } + + private TheRedTerror(final TheRedTerror card) { + super(card); + } + + @Override + public TheRedTerror copy() { + return new TheRedTerror(this); + } +} + +class TheRedTerrorTrigger extends TriggeredAbilityImpl implements BatchTriggeredAbility { + + public TheRedTerrorTrigger() { + super(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.P1P1.createInstance(1))); + setTriggerPhrase("Whenever a red source you control deals damage to one or more permanents and/or players"); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_BY_SOURCE; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + DamagedBatchBySourceEvent batchBySourceEvent = (DamagedBatchBySourceEvent) event; + + UUID sourceController = null; + MageObject sourceObject; + Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(batchBySourceEvent.getSourceId()); + if (sourcePermanent == null) { + sourceObject = game.getObject(event.getSourceId()); + if (sourceObject instanceof Controllable) { + sourceController = ((Controllable) sourceObject).getControllerId(); + } + } else { + sourceObject = sourcePermanent; + sourceController = sourcePermanent.getControllerId(); + } + + return sourceObject.getColor(game).isRed() // a red source + && getControllerId().equals(sourceController) // you control + && batchBySourceEvent.getAmount() > 0; // deals damage (skipping the permanent and/or player check, they are the only damageable) + } + + private TheRedTerrorTrigger(final TheRedTerrorTrigger trigger) { + super(trigger); + } + + @Override + public TheRedTerrorTrigger copy() { + return new TheRedTerrorTrigger(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TheSeventhDoctor.java b/Mage.Sets/src/mage/cards/t/TheSeventhDoctor.java new file mode 100644 index 00000000000..7990b780328 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheSeventhDoctor.java @@ -0,0 +1,104 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.dynamicvalue.common.ArtifactYouControlCount; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.keyword.InvestigateEffect; +import mage.abilities.hint.common.ArtifactYouControlHint; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInHand; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TheSeventhDoctor extends CardImpl { + + public TheSeventhDoctor(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.TIME_LORD); + this.subtype.add(SubType.DOCTOR); + this.power = new MageInt(3); + this.toughness = new MageInt(6); + + // Whenever The Seventh Doctor attacks, choose a card in your hand. Defending player guesses whether that card's mana value is greater than the number of artifacts you control. If they guessed wrong, you may cast it without paying its mana cost. If you don't cast a spell this way, investigate. + this.addAbility(new AttacksTriggeredAbility( + new TheSeventhDoctorEffect(), false, null, SetTargetPointer.PLAYER + ).addHint(ArtifactYouControlHint.instance)); + } + + private TheSeventhDoctor(final TheSeventhDoctor card) { + super(card); + } + + @Override + public TheSeventhDoctor copy() { + return new TheSeventhDoctor(this); + } +} + +class TheSeventhDoctorEffect extends OneShotEffect { + + TheSeventhDoctorEffect() { + super(Outcome.Benefit); + staticText = "choose a card in your hand. Defending player guesses whether that card's mana value " + + "is greater than the number of artifacts you control. If they guessed wrong, " + + "you may cast it without paying its mana cost. If you don't cast a spell this way, investigate"; + } + + private TheSeventhDoctorEffect(final TheSeventhDoctorEffect effect) { + super(effect); + } + + @Override + public TheSeventhDoctorEffect copy() { + return new TheSeventhDoctorEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + if (controller.getHand().isEmpty()) { + InvestigateEffect.doInvestigate(source.getControllerId(), 1, game, source); + return true; + } + TargetCard target = new TargetCardInHand(); + controller.choose(Outcome.PlayForFree, controller.getHand(), target, source, game); + Card card = game.getCard(target.getFirstTarget()); + game.informPlayers(controller.getLogName() + " has chosen a card in their hand"); + Player defender = game.getPlayer(getTargetPointer().getFirst(game, source)); + if (defender == null || card == null) { + InvestigateEffect.doInvestigate(source.getControllerId(), 1, game, source); + return true; + } + int count = ArtifactYouControlCount.instance.calculate(game, source, this); + boolean guessedGreater = defender.chooseUse( + outcome, "Is the chosen card's mana value greater than the number of artifacts " + + controller.getName() + " controls?", controller.getName() + " controls " + + count + " artifacts", "Greater than", "Not greater than", source, game + ); + game.informPlayers(defender.getLogName() + + " has guessed that the chosen card's mana value is " + (guessedGreater ? "" : "not ") + + "greater than the number of artifacts controlled by " + controller.getLogName()); + if (card.getManaValue() > count == guessedGreater + || !CardUtil.castSpellWithAttributesForFree(controller, source, game, card)) { + InvestigateEffect.doInvestigate(source.getControllerId(), 1, game, source); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TheTenthDoctor.java b/Mage.Sets/src/mage/cards/t/TheTenthDoctor.java index 80306587ab2..5afb16eb8bb 100644 --- a/Mage.Sets/src/mage/cards/t/TheTenthDoctor.java +++ b/Mage.Sets/src/mage/cards/t/TheTenthDoctor.java @@ -1,21 +1,17 @@ package mage.cards.t; import mage.MageInt; -import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.common.ActivateAsSorceryActivatedAbility; import mage.abilities.common.AttacksWithCreaturesTriggeredAbility; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.InfoEffect; -import mage.abilities.effects.common.continuous.GainSuspendEffect; import mage.abilities.effects.common.counter.TimeTravelEffect; import mage.abilities.keyword.SuspendAbility; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.counters.CounterType; import mage.game.Game; import mage.players.Player; @@ -38,9 +34,8 @@ public final class TheTenthDoctor extends CardImpl { // Timey-Wimey — {7}: Time travel three times. Activate only as a sorcery. (For each suspended card you own and each permanent you control with a time counter on it, you may add or remove a time counter. Then do it two more times.) Ability ability = new ActivateAsSorceryActivatedAbility(new TimeTravelEffect().setText("Time travel three times"), new GenericManaCost(7)); - ability.addEffect(new TimeTravelEffect().setText("")); - ability.addEffect(new TimeTravelEffect().setText("")); - ability.addEffect(new InfoEffect("(For each suspended card you own and each permanent you control with a time counter on it, you may add or remove a time counter. Then do it two more times.)")); + ability.addEffect(new TimeTravelEffect().setText("(For each suspended card you own and each permanent you control with a time counter on it")); + ability.addEffect(new TimeTravelEffect().setText(", you may add or remove a time counter. Then do it two more times.)")); this.addAbility(ability.withFlavorWord("Timey-Wimey")); } @@ -73,19 +68,11 @@ class TheTenthDoctorEffect extends OneShotEffect { return false; } for (Card card : controller.getLibrary().getCards(game)) { - if (!card.isLand(game)) { - boolean hasSuspend = card.getAbilities(game).containsClass(SuspendAbility.class); - UUID exileId = SuspendAbility.getSuspendExileId(controller.getId(), game); - if (controller.moveCardToExileWithInfo(card, exileId, "Suspended cards of " + controller.getName(), source, game, Zone.LIBRARY, true)) { - card.addCounters(CounterType.TIME.createInstance(3), source.getControllerId(), source, game); - if (!hasSuspend) { - game.addEffect(new GainSuspendEffect(new MageObjectReference(card, game)), source); - } - game.informPlayers(controller.getLogName() + " suspends 3 - " + card.getName()); - } - break; - } controller.moveCards(card, Zone.EXILED, source, game); + if (!card.isLand(game)) { + SuspendAbility.addTimeCountersAndSuspend(card, 3, source, game); + return true; + } } return true; } diff --git a/Mage.Sets/src/mage/cards/t/TheToymakersTrap.java b/Mage.Sets/src/mage/cards/t/TheToymakersTrap.java new file mode 100644 index 00000000000..3207b2c743c --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheToymakersTrap.java @@ -0,0 +1,131 @@ +package mage.cards.t; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.choices.Choice; +import mage.choices.ChoiceImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetPlayer; +import mage.target.common.TargetOpponent; +import mage.util.CardUtil; +import mage.util.RandomUtil; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author TheElk801 + */ +public final class TheToymakersTrap extends CardImpl { + + public TheToymakersTrap(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{B}"); + + // At the beginning of your upkeep, secretly choose a number between 1 and 5 that hasn't been chosen. If you do, an opponent guesses which number you chose, then you reveal the number you chose. If they guessed wrong, they lose life equal to the number they guessed and you draw a card. If they guessed right, sacrifice The Toymaker's Trap. + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new TheToymakersTrapEffect())); + } + + private TheToymakersTrap(final TheToymakersTrap card) { + super(card); + } + + @Override + public TheToymakersTrap copy() { + return new TheToymakersTrap(this); + } +} + +class TheToymakersTrapEffect extends OneShotEffect { + + private static final Set options = new HashSet<>(Arrays.asList("1", "2", "3", "4", "5")); + + TheToymakersTrapEffect() { + super(Outcome.Benefit); + staticText = "secretly choose a number between 1 and 5 that hasn't been chosen. If you do, " + + "an opponent guesses which number you chose, then you reveal the number you chose. " + + "If they guessed wrong, they lose life equal to the number they guessed and you draw a card. " + + "If they guessed right, sacrifice {this}"; + } + + private TheToymakersTrapEffect(final TheToymakersTrapEffect effect) { + super(effect); + } + + @Override + public TheToymakersTrapEffect copy() { + return new TheToymakersTrapEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + List alreadyChosen = getOrSetValue(game, source); + Set choices = new HashSet<>(options); + choices.removeIf(alreadyChosen::contains); + if (choices.isEmpty()) { + return false; + } + String number; + switch (choices.size()) { + case 0: + return false; + case 1: + number = RandomUtil.randomFromCollection(choices); + break; + default: + Choice choice = new ChoiceImpl(true); + choice.setMessage("Secretly choose a number which hasn't been chosen"); + choice.setChoices(choices); + controller.choose(outcome, choice, game); + number = choice.getChoice(); + } + game.informPlayers(controller.getLogName() + " has secretly chosen a number"); + TargetPlayer target = new TargetOpponent(true); + controller.choose(outcome, target, source, game); + Player opponent = game.getPlayer(target.getFirstTarget()); + if (opponent == null) { + return false; + } + Choice choice = new ChoiceImpl(true); + choice.setMessage("Guess which number was chosen"); + choice.setChoices(options); + opponent.choose(outcome, choice, game); + String guess = choice.getChoice(); + game.informPlayers(opponent.getLogName() + " has guessed " + guess); + game.informPlayers("The chosen number was " + number); + alreadyChosen.add(number); + alreadyChosen.sort(String::compareTo); + Optional.ofNullable(source.getSourcePermanentIfItStillExists(game)) + .ifPresent(permanent -> permanent.addInfo( + "CHOSEN_NUMBERS", CardUtil.addToolTipMarkTags( + "Chosen numbers: " + alreadyChosen.stream().collect(Collectors.joining(", ")) + ), game + )); + if (!Objects.equals(number, guess)) { + opponent.loseLife(Integer.parseInt(guess), game, source, false); + controller.drawCards(1, source, game); + } else { + Optional.ofNullable(source.getSourcePermanentIfItStillExists(game)) + .ifPresent(permanent -> permanent.sacrifice(source, game)); + } + return true; + } + + private static List getOrSetValue(Game game, Ability source) { + String key = "chosenNumbers_" + source.getControllerId() + '_' + source.getSourceObjectZoneChangeCounter(); + List list = (List) game.getState().getValue(key); + if (list != null) { + return list; + } + return game.getState().setValue(key, new ArrayList<>()); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TheWarGames.java b/Mage.Sets/src/mage/cards/t/TheWarGames.java new file mode 100644 index 00000000000..1ca331ee441 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheWarGames.java @@ -0,0 +1,125 @@ +package mage.cards.t; + +import mage.abilities.Ability; +import mage.abilities.common.SagaAbility; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.costs.common.ExileTargetCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DoWhenCostPaid; +import mage.abilities.effects.common.ExileAllEffect; +import mage.abilities.effects.common.combat.GoadTargetEffect; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.game.Game; +import mage.game.permanent.token.Token; +import mage.game.permanent.token.WarriorToken; +import mage.target.common.TargetControlledPermanent; +import mage.target.targetpointer.FixedTargets; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * @author TheElk801 + */ +public final class TheWarGames extends CardImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent(SubType.WARRIOR, "Warrior creature"); + private static final FilterPermanent filter2 = new FilterPermanent(SubType.WARRIOR, "Warriors"); + private static final FilterControlledPermanent filter3 = new FilterControlledPermanent("nontoken creature you control"); + + static { + filter3.add(TokenPredicate.FALSE); + } + + public TheWarGames(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{W}{W}"); + + this.subtype.add(SubType.SAGA); + + // (As this Saga enters and after your draw step, add a lore counter. Sacrifice after IV.) + SagaAbility sagaAbility = new SagaAbility(this, SagaChapter.CHAPTER_IV); + + // I -- Each player creates three tapped 1/1 white Warrior creature tokens. The tokens are goaded for as long as The War Games remains on the battlefield. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_I, new TheWarGamesEffect()); + + // II, III -- Put a +1/+1 counter on each Warrior creature. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_II, SagaChapter.CHAPTER_III, + new AddCountersAllEffect(CounterType.P1P1.createInstance(), filter) + ); + + // IV -- You may exile a nontoken creature you control. When you do, exile all Warriors. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_IV, + new DoWhenCostPaid( + new ReflexiveTriggeredAbility(new ExileAllEffect(filter2), false), + new ExileTargetCost(new TargetControlledPermanent(filter3)), + "Exile a nontoken creature you control?" + ) + ); + this.addAbility(sagaAbility); + } + + private TheWarGames(final TheWarGames card) { + super(card); + } + + @Override + public TheWarGames copy() { + return new TheWarGames(this); + } +} + +class TheWarGamesEffect extends OneShotEffect { + + TheWarGamesEffect() { + super(Outcome.Benefit); + staticText = "each player creates three tapped 1/1 white Warrior creature tokens. " + + "The tokens are goaded for as long as {this} remains on the battlefield"; + } + + private TheWarGamesEffect(final TheWarGamesEffect effect) { + super(effect); + } + + @Override + public TheWarGamesEffect copy() { + return new TheWarGamesEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Set addedTokens = new HashSet<>(); + for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { + Token token = new WarriorToken(); + token.putOntoBattlefield(3, game, source, playerId, true, false); + addedTokens.addAll(token.getLastAddedTokenIds()); + } + if (addedTokens.isEmpty()) { + return false; + } + if (source.getSourceObjectIfItStillExists(game) != null) { + game.addEffect( + new GoadTargetEffect(Duration.UntilSourceLeavesBattlefield) + .setTargetPointer(new FixedTargets( + addedTokens.stream() + .map(game::getPermanent) + .filter(Objects::nonNull) + .collect(Collectors.toSet()), game + )), source + ); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TheWarringTriad.java b/Mage.Sets/src/mage/cards/t/TheWarringTriad.java new file mode 100644 index 00000000000..3c739b398cb --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheWarringTriad.java @@ -0,0 +1,99 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.common.MillCardsCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.LoseCreatureTypeSourceEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.choices.ManaChoice; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.game.Game; +import mage.target.TargetPlayer; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TheWarringTriad extends CardImpl { + + private static final DynamicValue xValue = new CardsInControllerGraveyardCount(); + + public TheWarringTriad(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{3}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.GOD); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // As long as there are fewer than eight cards in your graveyard, The Warring Triad isn't a creature. + this.addAbility(new SimpleStaticAbility(new LoseCreatureTypeSourceEffect(xValue, 8) + .setText("as long as there are fewer than eight cards in your graveyard, {this} isn't a creature"))); + + // {T}, Mill a card: Target player adds one mana of any color. + Ability ability = new SimpleActivatedAbility(new TheWarringTriadEffect(), new TapSourceCost()); + ability.addCost(new MillCardsCost(1)); + ability.addTarget(new TargetPlayer()); + this.addAbility(ability); + } + + private TheWarringTriad(final TheWarringTriad card) { + super(card); + } + + @Override + public TheWarringTriad copy() { + return new TheWarringTriad(this); + } +} + +class TheWarringTriadEffect extends OneShotEffect { + + TheWarringTriadEffect() { + super(Outcome.Benefit); + staticText = "target player adds one mana of any color"; + } + + private TheWarringTriadEffect(final TheWarringTriadEffect effect) { + super(effect); + } + + @Override + public TheWarringTriadEffect copy() { + return new TheWarringTriadEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Optional.ofNullable(getTargetPointer().getFirst(game, source)) + .map(game::getPlayer) + .ifPresent(player -> player.getManaPool().addMana( + ManaChoice.chooseAnyColor(player, game, 1), game, source + )); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TheWeddingOfRiverSong.java b/Mage.Sets/src/mage/cards/t/TheWeddingOfRiverSong.java new file mode 100644 index 00000000000..78cdb25f6ec --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheWeddingOfRiverSong.java @@ -0,0 +1,92 @@ +package mage.cards.t; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.counter.TimeTravelEffect; +import mage.abilities.keyword.SuspendAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInHand; +import mage.target.common.TargetOpponent; + +import java.util.Arrays; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TheWeddingOfRiverSong extends CardImpl { + + public TheWeddingOfRiverSong(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{W}"); + + // Draw two cards, then you may exile a nonland card from your hand with a number of time counters on it equal to its mana value. Then target opponent does the same. Cards exiled this way that don't have suspend gain suspend. + this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(2)); + this.getSpellAbility().addEffect(new TheWeddingOfRiverSongEffect()); + this.getSpellAbility().addTarget(new TargetOpponent()); + + // Time travel. + this.getSpellAbility().addEffect(new TimeTravelEffect().concatBy("
")); + } + + private TheWeddingOfRiverSong(final TheWeddingOfRiverSong card) { + super(card); + } + + @Override + public TheWeddingOfRiverSong copy() { + return new TheWeddingOfRiverSong(this); + } +} + +class TheWeddingOfRiverSongEffect extends OneShotEffect { + + TheWeddingOfRiverSongEffect() { + super(Outcome.Benefit); + staticText = ", then you may exile a nonland card from your hand " + + "with a number of time counters on it equal to its mana value. Then target opponent does the same. " + + "Cards exiled this way that don't have suspend gain suspend"; + } + + private TheWeddingOfRiverSongEffect(final TheWeddingOfRiverSongEffect effect) { + super(effect); + } + + @Override + public TheWeddingOfRiverSongEffect copy() { + return new TheWeddingOfRiverSongEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + for (Player player : Arrays.asList( + game.getPlayer(source.getControllerId()), + game.getPlayer(this.getTargetPointer().getFirst(game, source)) + )) { + if (player == null + || player.getHand().count(StaticFilters.FILTER_CARD_NON_LAND, game) < 1 + || source.isControlledBy(player.getId()) + && !player.chooseUse(outcome, "Suspend a nonland card from your hand?", source, game)) { + continue; + } + TargetCard target = new TargetCardInHand(StaticFilters.FILTER_CARD_NON_LAND); + player.choose(Outcome.PlayForFree, player.getHand(), target, source, game); + Card card = game.getCard(target.getFirstTarget()); + if (card == null) { + continue; + } + player.moveCards(card, Zone.EXILED, source, game); + SuspendAbility.addTimeCountersAndSuspend(card, card.getManaValue(), source, game); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/t/ThornmantleStriker.java b/Mage.Sets/src/mage/cards/t/ThornmantleStriker.java index d3be8fdef03..8812ec8c512 100644 --- a/Mage.Sets/src/mage/cards/t/ThornmantleStriker.java +++ b/Mage.Sets/src/mage/cards/t/ThornmantleStriker.java @@ -127,7 +127,7 @@ class ThornmantleStrikerEffect extends OneShotEffect { remainingCounters -= numCounters; int min = Math.max(0, countersLeftToRemove - remainingCounters); int max = Math.min(countersLeftToRemove, numCounters); - int toRemove = controller.getAmount(min, max, counterName + " counters to remove", game); + int toRemove = controller.getAmount(min, max, counterName + " counters to remove", source, game); // Sanity check in case of GUI bugs/disconnects toRemove = Math.max(toRemove, min); toRemove = Math.min(toRemove, max); diff --git a/Mage.Sets/src/mage/cards/t/ThreeDogGalaxyNewsDJ.java b/Mage.Sets/src/mage/cards/t/ThreeDogGalaxyNewsDJ.java new file mode 100644 index 00000000000..be6d2d08ff0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/ThreeDogGalaxyNewsDJ.java @@ -0,0 +1,170 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksWithCreaturesTriggeredAbility; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.costs.CompositeCost; +import mage.abilities.costs.Cost; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenCopyTargetEffect; +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.FilterAttackingCreature; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ThreeDogGalaxyNewsDJ extends CardImpl { + + public ThreeDogGalaxyNewsDJ(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.BARD); + this.power = new MageInt(1); + this.toughness = new MageInt(5); + + // Whenever you attack, you may pay {2} and sacrifice an Aura attached to Three Dog, Galaxy News DJ. When you sacrifice an Aura this way, for each other attacking creature you control, create a token that's a copy of that Aura attached to that creature. + this.addAbility(new AttacksWithCreaturesTriggeredAbility(new ThreeDogGalaxyNewsDJEffect(), 1)); + } + + private ThreeDogGalaxyNewsDJ(final ThreeDogGalaxyNewsDJ card) { + super(card); + } + + @Override + public ThreeDogGalaxyNewsDJ copy() { + return new ThreeDogGalaxyNewsDJ(this); + } +} + +class ThreeDogGalaxyNewsDJEffect extends OneShotEffect { + + private static final FilterPermanent filter = new FilterPermanent(SubType.AURA, "Aura attached to this creature"); + + static { + filter.add(ThreeDogGalaxyNewsDJPredicate.instance); + } + + enum ThreeDogGalaxyNewsDJPredicate implements ObjectSourcePlayerPredicate { + instance; + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + return input + .getSource() + .getSourceObjectIfItStillExists(game) != null + && Optional + .ofNullable(input) + .map(ObjectSourcePlayer::getObject) + .map(Permanent::getAttachedTo) + .map(input.getSourceId()::equals) + .orElse(false); + } + } + + ThreeDogGalaxyNewsDJEffect() { + super(Outcome.Benefit); + staticText = "you may pay {2} and sacrifice an Aura attached to {this}. " + + "When you sacrifice an Aura this way, for each other attacking creature you control, " + + "create a token that's a copy of that Aura attached to that creature"; + } + + private ThreeDogGalaxyNewsDJEffect(final ThreeDogGalaxyNewsDJEffect effect) { + super(effect); + } + + @Override + public ThreeDogGalaxyNewsDJEffect copy() { + return new ThreeDogGalaxyNewsDJEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + SacrificeTargetCost sacCost = new SacrificeTargetCost(filter); + Cost cost = new CompositeCost( + new GenericManaCost(2), sacCost, + "Pay {2} and sacrifice an Aura attached to this creature" + ); + if (!cost.canPay(source, source, source.getControllerId(), game) + || !player.chooseUse(outcome, "Pay {2} and sacrifice an Aura?", source, game) + || !cost.pay(source, game, source, source.getControllerId(), false)) { + return false; + } + Permanent permanent = sacCost + .getPermanents() + .stream() + .findFirst() + .orElse(null); + game.fireReflexiveTriggeredAbility(new ReflexiveTriggeredAbility( + new ThreeDogGalaxyNewsDJTokenEffect(permanent), false + ), source); + return true; + } +} + +class ThreeDogGalaxyNewsDJTokenEffect extends OneShotEffect { + + private static final FilterPermanent filter = new FilterAttackingCreature(); + + static { + filter.add(AnotherPredicate.instance); + } + + private final Permanent permanent; + + ThreeDogGalaxyNewsDJTokenEffect(Permanent permanent) { + super(Outcome.Benefit); + this.permanent = permanent != null ? permanent.copy() : null; + staticText = "for each other attacking creature you control, " + + "create a token that's a copy of that Aura attached to that creature"; + } + + private ThreeDogGalaxyNewsDJTokenEffect(final ThreeDogGalaxyNewsDJTokenEffect effect) { + super(effect); + this.permanent = effect.permanent != null ? effect.permanent.copy() : null; + } + + @Override + public ThreeDogGalaxyNewsDJTokenEffect copy() { + return new ThreeDogGalaxyNewsDJTokenEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + if (permanent == null) { + return false; + } + for (Permanent permanent : game.getBattlefield().getActivePermanents( + filter, source.getControllerId(), source, game + )) { + new CreateTokenCopyTargetEffect() + .setSavedPermanent(permanent) + .setAttachedTo(permanent.getId()) + .apply(game, source); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TifaLockhart.java b/Mage.Sets/src/mage/cards/t/TifaLockhart.java new file mode 100644 index 00000000000..0ef792afaf2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TifaLockhart.java @@ -0,0 +1,50 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.common.LandfallAbility; +import mage.abilities.dynamicvalue.common.SourcePermanentPowerValue; +import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TifaLockhart extends CardImpl { + + public TifaLockhart(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.MONK); + this.power = new MageInt(1); + this.toughness = new MageInt(2); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Landfall -- Whenever a land you control enters, double Tifa Lockhart's power until end of turn. + this.addAbility(new LandfallAbility(new BoostSourceEffect( + SourcePermanentPowerValue.ALLOW_NEGATIVE, + StaticValue.get(0), Duration.EndOfTurn + ).setText(" double {this}'s power until end of turn"))); + } + + private TifaLockhart(final TifaLockhart card) { + super(card); + } + + @Override + public TifaLockhart copy() { + return new TifaLockhart(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TifaMartialArtist.java b/Mage.Sets/src/mage/cards/t/TifaMartialArtist.java new file mode 100644 index 00000000000..748738310f9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TifaMartialArtist.java @@ -0,0 +1,64 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; +import mage.abilities.condition.common.FirstCombatPhaseCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.AdditionalCombatPhaseEffect; +import mage.abilities.effects.common.UntapAllEffect; +import mage.abilities.keyword.MeleeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.PowerPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TifaMartialArtist extends CardImpl { + + private static final FilterCreaturePermanent filter + = new FilterCreaturePermanent("creatures you control with power 7 or greater"); + + static { + filter.add(TargetController.YOU.getControllerPredicate()); + filter.add(new PowerPredicate(ComparisonType.MORE_THAN, 6)); + } + + public TifaMartialArtist(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}{G}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.MONK); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Melee + this.addAbility(new MeleeAbility()); + + // Whenever one or more creatures you control with power 7 or greater deal combat damage to a player, untap all creatures you control. If it's the first combat phase of your turn, there is an additional combat phase after this phase. + Ability ability = new OneOrMoreCombatDamagePlayerTriggeredAbility( + new UntapAllEffect(StaticFilters.FILTER_CONTROLLED_CREATURES), filter + ); + ability.addEffect(new ConditionalOneShotEffect( + new AdditionalCombatPhaseEffect(), FirstCombatPhaseCondition.instance, + "If it's the first combat phase of your turn, there is an additional combat phase after this phase" + )); + this.addAbility(ability); + } + + private TifaMartialArtist(final TifaMartialArtist card) { + super(card); + } + + @Override + public TifaMartialArtist copy() { + return new TifaMartialArtist(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TilonallisSummoner.java b/Mage.Sets/src/mage/cards/t/TilonallisSummoner.java index 3e2481b2b96..072b3afc92d 100644 --- a/Mage.Sets/src/mage/cards/t/TilonallisSummoner.java +++ b/Mage.Sets/src/mage/cards/t/TilonallisSummoner.java @@ -78,7 +78,7 @@ class TilonallisSummonerEffect extends OneShotEffect { if (controller != null) { ManaCosts cost = new ManaCostsImpl<>("{X}{R}"); if (controller.chooseUse(outcome, "Pay " + cost.getText() + "? If you do, you create X 1/1 red Elemental creature tokens that are tapped and attacking.", source, game)) { - int costX = controller.announceXMana(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source); + int costX = controller.announceX(0, Integer.MAX_VALUE, "Announce the value for {X} (pay for tokens)", game, source, true); cost.add(new GenericManaCost(costX)); if (cost.pay(source, game, source, source.getControllerId(), false, null)) { controller.resetStoredBookmark(game); // otherwise you can undo the payment diff --git a/Mage.Sets/src/mage/cards/t/TocasiasWelcome.java b/Mage.Sets/src/mage/cards/t/TocasiasWelcome.java index 3afaf39fb34..a83bf4f499c 100644 --- a/Mage.Sets/src/mage/cards/t/TocasiasWelcome.java +++ b/Mage.Sets/src/mage/cards/t/TocasiasWelcome.java @@ -27,10 +27,9 @@ public final class TocasiasWelcome extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{W}"); // Whenever one or more creatures with mana value 3 or less enter the battlefield under your control, draw a card. This ability triggers only once each turn. - this.addAbility(new EntersBattlefieldControlledTriggeredAbility( - new DrawCardSourceControllerEffect(1), filter - ).setTriggersLimitEachTurn(1).setTriggerPhrase("Whenever one or more creatures with mana value 3 " + - "or less enter the battlefield under your control, ")); + this.addAbility(new EntersBattlefieldControlledTriggeredAbility(new DrawCardSourceControllerEffect(1), filter) + .setTriggersLimitEachTurn(1) + .setTriggerPhrase("Whenever one or more creatures you control with mana value 3 or less enter, ")); } private TocasiasWelcome(final TocasiasWelcome card) { diff --git a/Mage.Sets/src/mage/cards/t/TombstoneStairwell.java b/Mage.Sets/src/mage/cards/t/TombstoneStairwell.java index d07567b164e..f6e31213604 100644 --- a/Mage.Sets/src/mage/cards/t/TombstoneStairwell.java +++ b/Mage.Sets/src/mage/cards/t/TombstoneStairwell.java @@ -1,22 +1,16 @@ package mage.cards.t; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; import mage.abilities.keyword.CumulativeUpkeepAbility; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.SuperType; -import mage.constants.TargetController; -import mage.constants.Zone; +import mage.constants.*; import mage.filter.StaticFilters; import mage.game.Game; import mage.game.events.GameEvent; @@ -28,8 +22,11 @@ import mage.players.Player; import mage.target.targetpointer.FixedTarget; import mage.util.CardUtil; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + /** - * * @author L_J */ public final class TombstoneStairwell extends CardImpl { @@ -75,7 +72,6 @@ class TombstoneStairwellCreateTokenEffect extends OneShotEffect { } @Override - @SuppressWarnings("unchecked") public boolean apply(Game game, Ability source) { Token token = new TombspawnZombieToken(); Player activePlayer = game.getPlayer(game.getActivePlayerId()); @@ -108,6 +104,7 @@ class TombstoneStairwellTriggeredAbility extends TriggeredAbilityImpl { TombstoneStairwellTriggeredAbility() { super(Zone.BATTLEFIELD, new TombstoneStairwellDestroyEffect(), false); + this.setLeavesTheBattlefieldTrigger(true); } private TombstoneStairwellTriggeredAbility(final TombstoneStairwellTriggeredAbility ability) { @@ -181,7 +178,6 @@ class TombstoneStairwellDestroyEffect extends OneShotEffect { } @Override - @SuppressWarnings("unchecked") public boolean apply(Game game, Ability source) { Object object = game.getState().getValue(cardZoneString); if (object != null) { diff --git a/Mage.Sets/src/mage/cards/t/ToughCookie.java b/Mage.Sets/src/mage/cards/t/ToughCookie.java index 0e7eaa28916..e7ef120c870 100644 --- a/Mage.Sets/src/mage/cards/t/ToughCookie.java +++ b/Mage.Sets/src/mage/cards/t/ToughCookie.java @@ -56,7 +56,7 @@ public final class ToughCookie extends CardImpl { this.addAbility(ability); // {2}, {T}, Sacrifice Tough Cookie: You gain 3 life. - this.addAbility(new FoodAbility(true)); + this.addAbility(new FoodAbility()); } private ToughCookie(final ToughCookie card) { diff --git a/Mage.Sets/src/mage/cards/t/TownGreeter.java b/Mage.Sets/src/mage/cards/t/TownGreeter.java new file mode 100644 index 00000000000..566f511acee --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TownGreeter.java @@ -0,0 +1,89 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TownGreeter extends CardImpl { + + public TownGreeter(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.CITIZEN); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // When this creature enters, mill four cards. You may put a land card from among them into your hand. If you put a Town card into your hand this way, you gain 2 life. + this.addAbility(new EntersBattlefieldTriggeredAbility(new TownGreeterEffect())); + } + + private TownGreeter(final TownGreeter card) { + super(card); + } + + @Override + public TownGreeter copy() { + return new TownGreeter(this); + } +} + +class TownGreeterEffect extends OneShotEffect { + + TownGreeterEffect() { + super(Outcome.Benefit); + staticText = "mill four cards. You may put a land card from among them into your hand. " + + "If you put a Town card into your hand this way, you gain 2 life"; + } + + private TownGreeterEffect(final TownGreeterEffect effect) { + super(effect); + } + + @Override + public TownGreeterEffect copy() { + return new TownGreeterEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Cards cards = player.millCards(4, source, game); + if (cards.isEmpty()) { + return false; + } + TargetCard target = new TargetCard(0, 1, Zone.ALL, StaticFilters.FILTER_CARD_LAND_A); + target.withNotTarget(true); + player.choose(Outcome.DrawCard, cards, target, source, game); + Card card = game.getCard(target.getFirstTarget()); + if (card == null) { + return true; + } + player.moveCards(card, Zone.HAND, source, game); + if (card.hasSubtype(SubType.TOWN, game)) { + player.gainLife(2, game, source); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TradeSecrets.java b/Mage.Sets/src/mage/cards/t/TradeSecrets.java index 97972b6afff..9c08ff879f0 100644 --- a/Mage.Sets/src/mage/cards/t/TradeSecrets.java +++ b/Mage.Sets/src/mage/cards/t/TradeSecrets.java @@ -59,11 +59,11 @@ class TradeSecretsEffect extends OneShotEffect { if (controller != null && targetOpponent != null) { new DrawCardTargetEffect(2).apply(game, source);//The drawcard method would not work immediately - int amountOfCardsToDraw = controller.getAmount(0, 4, message2, game); + int amountOfCardsToDraw = controller.getAmount(0, 4, message2, source, game); new DrawCardSourceControllerEffect(amountOfCardsToDraw).apply(game, source); while (targetOpponent.chooseUse(Outcome.AIDontUseIt, message, source, game)) { new DrawCardTargetEffect(2).apply(game, source); - amountOfCardsToDraw = controller.getAmount(0, 4, message2, game); + amountOfCardsToDraw = controller.getAmount(0, 4, message2, source, game); new DrawCardSourceControllerEffect(amountOfCardsToDraw).apply(game, source); } return true; diff --git a/Mage.Sets/src/mage/cards/t/TragicBanshee.java b/Mage.Sets/src/mage/cards/t/TragicBanshee.java index e6b4d7480d9..b31ebfd7d57 100644 --- a/Mage.Sets/src/mage/cards/t/TragicBanshee.java +++ b/Mage.Sets/src/mage/cards/t/TragicBanshee.java @@ -34,7 +34,7 @@ public final class TragicBanshee extends CardImpl { new AddContinuousEffectToGame(new BoostTargetEffect(-13, -13)), new AddContinuousEffectToGame(new BoostTargetEffect(-1, -1)), MorbidCondition.instance, "target creature an opponent controls gets -1/-1 until end of turn. " + - "If a creature died this turn, that creature gets -13/-13 instead" + "If a creature died this turn, that creature gets -13/-13 until end of turn instead" )); ability.addTarget(new TargetOpponentsCreaturePermanent()); this.addAbility(ability.addHint(MorbidHint.instance).setAbilityWord(AbilityWord.MORBID)); diff --git a/Mage.Sets/src/mage/cards/t/TranceKujaFateDefied.java b/Mage.Sets/src/mage/cards/t/TranceKujaFateDefied.java new file mode 100644 index 00000000000..c16551c9f7d --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TranceKujaFateDefied.java @@ -0,0 +1,84 @@ +package mage.cards.t; + +import java.util.Optional; +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.*; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.util.CardUtil; + +/** + * @author balazskristof + */ +public final class TranceKujaFateDefied extends CardImpl { + + public TranceKujaFateDefied(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.AVATAR); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(4); + this.toughness = new MageInt(6); + + this.color.setBlack(true); + this.color.setRed(true); + + this.nightCard = true; + + // Flame Star -- If a Wizard you control would deal damage to a permanent or player, it deals double that damage instead. + this.addAbility(new SimpleStaticAbility(new TranceKujaFateDefiedEffect()).withFlavorWord("Flame Star")); + } + + private TranceKujaFateDefied(final TranceKujaFateDefied card) { + super(card); + } + + @Override + public TranceKujaFateDefied copy() { + return new TranceKujaFateDefied(this); + } +} + +class TranceKujaFateDefiedEffect extends ReplacementEffectImpl { + + TranceKujaFateDefiedEffect() { + super(Duration.WhileOnBattlefield, Outcome.Damage); + staticText = "If a Wizard you control would deal damage to a permanent or player, it deals double that damage instead."; + } + + private TranceKujaFateDefiedEffect(final TranceKujaFateDefiedEffect effect) { + super(effect); + } + + @Override + public TranceKujaFateDefiedEffect copy() { + return new TranceKujaFateDefiedEffect(this); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType().equals(GameEvent.EventType.DAMAGE_PLAYER) + || event.getType().equals(GameEvent.EventType.DAMAGE_PERMANENT); + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + return game.getControllerId(event.getSourceId()).equals(source.getControllerId()) + && Optional.ofNullable(game.getObject(event.getSourceId())) + .map(object -> object.hasSubtype(SubType.WIZARD, game)) + .orElse(false); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + event.setAmount(CardUtil.overflowMultiply(event.getAmount(), 2)); + return false; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/t/Transpose.java b/Mage.Sets/src/mage/cards/t/Transpose.java new file mode 100644 index 00000000000..fd6a36e7dad --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/Transpose.java @@ -0,0 +1,68 @@ +package mage.cards.t; + +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DrawDiscardControllerEffect; +import mage.abilities.effects.common.LoseLifeSourceControllerEffect; +import mage.abilities.keyword.ReboundAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.permanent.token.BlackWizardToken; +import mage.game.stack.Spell; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Transpose extends CardImpl { + + public Transpose(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{B}"); + + // Draw a card, then discard a card. You lose 1 life. If this spell was cast from your hand, create a 0/1 black Wizard creature token with "Whenever you cast a noncreature spell, this token deals 1 damage to each opponent." + this.getSpellAbility().addEffect(new DrawDiscardControllerEffect(1, 1)); + this.getSpellAbility().addEffect(new LoseLifeSourceControllerEffect(1)); + this.getSpellAbility().addEffect(new ConditionalOneShotEffect( + new CreateTokenEffect(new BlackWizardToken()), TransposeCondition.instance + )); + + // Rebound + this.addAbility(new ReboundAbility()); + } + + private Transpose(final Transpose card) { + super(card); + } + + @Override + public Transpose copy() { + return new Transpose(this); + } +} + +enum TransposeCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + return Optional + .ofNullable(source) + .map(Ability::getSourceId) + .map(game::getSpell) + .map(Spell::getFromZone) + .map(Zone.GRAVEYARD::match) + .orElse(false); + } + + @Override + public String toString() { + return "this spell was cast from your hand"; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TravelingChocobo.java b/Mage.Sets/src/mage/cards/t/TravelingChocobo.java new file mode 100644 index 00000000000..f66dead4ac6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TravelingChocobo.java @@ -0,0 +1,113 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; +import mage.abilities.effects.common.continuous.PlayFromTopOfLibraryEffect; +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.filter.FilterCard; +import mage.filter.predicate.Predicates; +import mage.game.Game; +import mage.game.events.EntersTheBattlefieldEvent; +import mage.game.events.GameEvent; +import mage.game.events.NumberOfTriggersEvent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TravelingChocobo extends CardImpl { + + private static final FilterCard filter = new FilterCard("play lands and cast Bird spells"); + + static { + filter.add(Predicates.or( + CardType.LAND.getPredicate(), + SubType.BIRD.getPredicate() + )); + } + + public TravelingChocobo(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.subtype.add(SubType.BIRD); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // You may look at the top card of your library any time. + this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); + + // You may play lands and cast Bird spells from the top of your library. + this.addAbility(new SimpleStaticAbility(new PlayFromTopOfLibraryEffect(filter))); + + // If a land or Bird you control entering the battlefield causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time. + this.addAbility(new SimpleStaticAbility(new TravelingChocoboEffect())); + } + + private TravelingChocobo(final TravelingChocobo card) { + super(card); + } + + @Override + public TravelingChocobo copy() { + return new TravelingChocobo(this); + } +} + +class TravelingChocoboEffect extends ReplacementEffectImpl { + + TravelingChocoboEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit); + staticText = "if land or Bird entering the battlefield causes a triggered ability " + + "of a permanent you control to trigger, that ability triggers an additional time"; + } + + private TravelingChocoboEffect(final TravelingChocoboEffect effect) { + super(effect); + } + + @Override + public TravelingChocoboEffect copy() { + return new TravelingChocoboEffect(this); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.NUMBER_OF_TRIGGERS; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + if (!(event instanceof NumberOfTriggersEvent)) { + return false; + } + NumberOfTriggersEvent numberOfTriggersEvent = (NumberOfTriggersEvent) event; + if (!source.isControlledBy(event.getPlayerId())) { + return false; + } + GameEvent sourceEvent = numberOfTriggersEvent.getSourceEvent(); + if (sourceEvent == null + || sourceEvent.getType() != GameEvent.EventType.ENTERS_THE_BATTLEFIELD + || !(sourceEvent instanceof EntersTheBattlefieldEvent)) { + return false; + } + EntersTheBattlefieldEvent entersTheBattlefieldEvent = (EntersTheBattlefieldEvent) sourceEvent; + return (entersTheBattlefieldEvent.getTarget().isLand(game) + || entersTheBattlefieldEvent.getTarget().hasSubtype(SubType.BIRD, game)) + && game.getPermanent(numberOfTriggersEvent.getSourceId()) != null; + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + event.setAmount(event.getAmount() + 1); + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TraverseEternity.java b/Mage.Sets/src/mage/cards/t/TraverseEternity.java index d8fdb742312..e0b832bb37d 100644 --- a/Mage.Sets/src/mage/cards/t/TraverseEternity.java +++ b/Mage.Sets/src/mage/cards/t/TraverseEternity.java @@ -25,7 +25,7 @@ public final class TraverseEternity extends CardImpl { // Draw cards equal to the highest mana value among historic permanents you control. this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(TraverseEternityValue.instance) - .setText("draw cards equal to the highest mana value among historic permanents you control")); + .setText("draw cards equal to the greatest mana value among historic permanents you control")); this.getSpellAbility().addHint(TraverseEternityValue.getHint()); } @@ -41,7 +41,7 @@ public final class TraverseEternity extends CardImpl { enum TraverseEternityValue implements DynamicValue { instance; - private static final Hint hint = new ValueHint("Highest mana value among your historic permanents", instance); + private static final Hint hint = new ValueHint("Greatest mana value among your historic permanents", instance); public static Hint getHint() { return hint; diff --git a/Mage.Sets/src/mage/cards/t/TrialOfATimeLord.java b/Mage.Sets/src/mage/cards/t/TrialOfATimeLord.java new file mode 100644 index 00000000000..685dcb98b8d --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TrialOfATimeLord.java @@ -0,0 +1,97 @@ +package mage.cards.t; + +import mage.abilities.Ability; +import mage.abilities.common.SagaAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileUntilSourceLeavesEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.choices.TwoChoiceVote; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SagaChapter; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterOpponentsCreaturePermanent; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.game.ExileZone; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TrialOfATimeLord extends CardImpl { + + private static final FilterPermanent filter + = new FilterOpponentsCreaturePermanent("nontoken creature an opponent controls"); + + static { + filter.add(TokenPredicate.FALSE); + } + + public TrialOfATimeLord(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{W}{W}"); + + this.subtype.add(SubType.SAGA); + + // (As this Saga enters and after your draw step, add a lore counter. Sacrifice after IV.) + SagaAbility sagaAbility = new SagaAbility(this, SagaChapter.CHAPTER_IV); + + // I, II, III -- Exile target nontoken creature an opponent controls until Trial of a Time Lord leaves the battlefield. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_I, SagaChapter.CHAPTER_III, + new ExileUntilSourceLeavesEffect(), new TargetPermanent(filter) + ); + + // IV -- Starting with you, each player votes for innocent or guilty. If guilty gets more votes, the owner of each card exiled with Trial of a Time Lord puts that card on the bottom of their library. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_IV, new TrialOfATimeLordEffect()); + this.addAbility(sagaAbility); + } + + private TrialOfATimeLord(final TrialOfATimeLord card) { + super(card); + } + + @Override + public TrialOfATimeLord copy() { + return new TrialOfATimeLord(this); + } +} + +class TrialOfATimeLordEffect extends OneShotEffect { + + TrialOfATimeLordEffect() { + super(Outcome.Benefit); + staticText = "starting with you, each player votes for innocent or guilty. If guilty gets more votes, " + + "the owner of each card exiled with {this} puts that card on the bottom of their library"; + } + + private TrialOfATimeLordEffect(final TrialOfATimeLordEffect effect) { + super(effect); + } + + @Override + public TrialOfATimeLordEffect copy() { + return new TrialOfATimeLordEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + TwoChoiceVote vote = new TwoChoiceVote("Innocent", "Guilty", Outcome.Detriment); + vote.doVotes(source, game); + if (vote.getMostVoted().contains(Boolean.TRUE)) { + return true; + } + Player player = game.getPlayer(source.getControllerId()); + ExileZone exileZone = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source)); + if (player != null && exileZone != null && !exileZone.isEmpty()) { + player.putCardsOnBottomOfLibrary(exileZone, game, source, false); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TrialOfAgony.java b/Mage.Sets/src/mage/cards/t/TrialOfAgony.java new file mode 100644 index 00000000000..3b4f84a100d --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TrialOfAgony.java @@ -0,0 +1,120 @@ +package mage.cards.t; + +import mage.MageItem; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +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.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.permanent.ControllerIdPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.common.TargetPermanentSameController; +import mage.target.targetpointer.FixedTarget; +import mage.util.RandomUtil; + +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * @author TheElk801 + */ +public final class TrialOfAgony extends CardImpl { + + public TrialOfAgony(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{R}"); + + // Choose two target creatures controlled by the same opponent. That player chooses one of those creatures. Trial of Agony deals 5 damage to that creature, and the other can't block this turn. + this.getSpellAbility().addEffect(new TrialOfAgonyEffect()); + this.getSpellAbility().addTarget(new TargetPermanentSameController(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURES)); + } + + private TrialOfAgony(final TrialOfAgony card) { + super(card); + } + + @Override + public TrialOfAgony copy() { + return new TrialOfAgony(this); + } +} + +class TrialOfAgonyEffect extends OneShotEffect { + + TrialOfAgonyEffect() { + super(Outcome.Benefit); + staticText = "choose two target creatures controlled by the same opponent. " + + "That player chooses one of those creatures. " + + "{this} deals 5 damage to that creature, and the other can't block this turn"; + } + + private TrialOfAgonyEffect(final TrialOfAgonyEffect effect) { + super(effect); + } + + @Override + public TrialOfAgonyEffect copy() { + return new TrialOfAgonyEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + List permanents = this + .getTargetPointer() + .getTargets(game, source) + .stream() + .map(game::getPermanent) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + Permanent toDamage; + Permanent cantBlock; + switch (permanents.size()) { + case 0: + return false; + case 1: + toDamage = permanents.get(0); + cantBlock = null; + break; + default: + Player player = game.getPlayer(permanents.get(0).getControllerId()); + if (player == null) { + toDamage = permanents.get(0); + cantBlock = permanents.get(1); + break; + } + FilterPermanent filter = new FilterPermanent(); + filter.add(Predicates.or( + permanents + .stream() + .map(MageItem::getId) + .map(ControllerIdPredicate::new) + .collect(Collectors.toList()) + )); + TargetPermanent target = new TargetPermanent(filter); + target.withChooseHint("to deal damage to"); + target.withNotTarget(true); + player.choose(outcome, target, source, game); + toDamage = game.getPermanent(target.getFirstTarget()); + permanents.remove(toDamage); + cantBlock = RandomUtil.randomFromCollection(permanents); + } + if (toDamage != null) { + toDamage.damage(5, source, game); + } + if (cantBlock != null) { + game.addEffect(new CantBlockTargetEffect(Duration.EndOfTurn) + .setTargetPointer(new FixedTarget(cantBlock, game)), source); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TributeToHunger.java b/Mage.Sets/src/mage/cards/t/TributeToHunger.java index 51bdb528490..fbff8aca089 100644 --- a/Mage.Sets/src/mage/cards/t/TributeToHunger.java +++ b/Mage.Sets/src/mage/cards/t/TributeToHunger.java @@ -45,7 +45,7 @@ class TributeToHungerEffect extends OneShotEffect { TributeToHungerEffect() { super(Outcome.Sacrifice); - staticText = "Target opponent sacrifices a creature. You gain life equal to that creature's toughness"; + staticText = "Target opponent sacrifices a creature of their choice. You gain life equal to that creature's toughness"; } private TributeToHungerEffect(final TributeToHungerEffect effect) { diff --git a/Mage.Sets/src/mage/cards/t/TripleTriad.java b/Mage.Sets/src/mage/cards/t/TripleTriad.java new file mode 100644 index 00000000000..b9a61b59d3f --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TripleTriad.java @@ -0,0 +1,86 @@ +package mage.cards.t; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.asthought.PlayFromNotOwnHandZoneTargetEffect; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; +import mage.cards.*; +import mage.constants.*; +import mage.game.Game; +import mage.players.Player; +import mage.target.targetpointer.FixedTargets; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TripleTriad extends CardImpl { + + public TripleTriad(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{R}{R}{R}"); + + // At the beginning of your upkeep, each player exiles the top card of their library. Until end of turn, you may play the card you own exiled this way and each other card exiled this way with lesser mana value than it without paying their mana costs. + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new TripleTriadEffect())); + } + + private TripleTriad(final TripleTriad card) { + super(card); + } + + @Override + public TripleTriad copy() { + return new TripleTriad(this); + } +} + +class TripleTriadEffect extends OneShotEffect { + + TripleTriadEffect() { + super(Outcome.Benefit); + staticText = "each player exiles the top card of their library. Until end of turn, " + + "you may play the card you own exiled this way and each other card exiled this way " + + "with lesser mana value than it without paying their mana costs"; + } + + private TripleTriadEffect(final TripleTriadEffect effect) { + super(effect); + } + + @Override + public TripleTriadEffect copy() { + return new TripleTriadEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Cards cards = new CardsImpl(); + for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { + Player player = game.getPlayer(playerId); + if (player == null) { + return false; + } + Card card = player.getLibrary().getFromTop(game); + if (card == null) { + continue; + } + player.moveCards(card, Zone.EXILED, source, game); + cards.add(card); + } + Card card = cards + .getCards(game) + .stream() + .filter(c -> c.isOwnedBy(source.getControllerId())) + .findAny() + .orElse(null); + if (card == null) { + return true; + } + cards.removeIf(uuid -> game.getCard(uuid).getManaValue() >= card.getManaValue()); + cards.add(card); + game.addEffect(new PlayFromNotOwnHandZoneTargetEffect( + Zone.EXILED, TargetController.YOU, Duration.EndOfTurn, true, false + ).setTargetPointer(new FixedTargets(cards, game)), source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TromellSeymoursButler.java b/Mage.Sets/src/mage/cards/t/TromellSeymoursButler.java new file mode 100644 index 00000000000..53baa22c379 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TromellSeymoursButler.java @@ -0,0 +1,112 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.EntersWithCountersControlledEffect; +import mage.abilities.effects.common.counter.ProliferateEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.permanent.EnteredThisTurnPredicate; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.game.Game; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TromellSeymoursButler extends CardImpl { + + public TromellSeymoursButler(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.ELF); + this.subtype.add(SubType.ADVISOR); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Each other nontoken creature you control enters with an additional +1/+1 counter on it. + this.addAbility(new SimpleStaticAbility(new EntersWithCountersControlledEffect( + StaticFilters.FILTER_CONTROLLED_CREATURE_NON_TOKEN, + CounterType.P1P1.createInstance(), true + ))); + + // {1}, {T}: Proliferate X times, where X is the number of nontoken creatures you control that entered this turn. + Ability ability = new SimpleActivatedAbility(new TromellSeymoursButlerEffect(), new GenericManaCost(1)); + ability.addCost(new TapSourceCost()); + this.addAbility(ability.addHint(TromellSeymoursButlerEffect.getHint())); + } + + private TromellSeymoursButler(final TromellSeymoursButler card) { + super(card); + } + + @Override + public TromellSeymoursButler copy() { + return new TromellSeymoursButler(this); + } +} + +class TromellSeymoursButlerEffect extends OneShotEffect { + + private static final FilterPermanent filter = new FilterControlledCreaturePermanent(); + + static { + filter.add(TokenPredicate.FALSE); + filter.add(EnteredThisTurnPredicate.instance); + } + + private static final Hint hint = new ValueHint( + "Nontoken creatures you control that entered this turn", new PermanentsOnBattlefieldCount(filter) + ); + + TromellSeymoursButlerEffect() { + super(Outcome.Benefit); + staticText = "proliferate X times, where X is the number of " + + "nontoken creatures you control that entered this turn"; + } + + private TromellSeymoursButlerEffect(final TromellSeymoursButlerEffect effect) { + super(effect); + } + + @Override + public TromellSeymoursButlerEffect copy() { + return new TromellSeymoursButlerEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + int count = game.getBattlefield().count(filter, source.getControllerId(), source, game); + if (count < 1) { + return false; + } + Effect effect = new ProliferateEffect(); + for (int i = 0; i < count; i++) { + effect.apply(game, source); + } + return true; + } + + public static Hint getHint() { + return hint; + } +} diff --git a/Mage.Sets/src/mage/cards/t/Truce.java b/Mage.Sets/src/mage/cards/t/Truce.java index d0b4f90e7b4..7ecc0b5e213 100644 --- a/Mage.Sets/src/mage/cards/t/Truce.java +++ b/Mage.Sets/src/mage/cards/t/Truce.java @@ -57,7 +57,7 @@ class TruceEffect extends OneShotEffect { for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { Player player = game.getPlayer(playerId); if (player != null) { - int cardsToDraw = player.getAmount(0, 2, "Draw how many cards?", game); + int cardsToDraw = player.getAmount(0, 2, "Draw how many cards?", source, game); player.drawCards(cardsToDraw, source, game); player.gainLife((2 - cardsToDraw) * 2, game, source); } diff --git a/Mage.Sets/src/mage/cards/u/UltimaOriginOfOblivion.java b/Mage.Sets/src/mage/cards/u/UltimaOriginOfOblivion.java new file mode 100644 index 00000000000..54e0ab0e7d0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/UltimaOriginOfOblivion.java @@ -0,0 +1,150 @@ +package mage.cards.u; + +import mage.MageInt; +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.effects.mana.BasicManaEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.mana.ColorlessManaAbility; +import mage.abilities.mana.TriggeredManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.TappedForManaEvent; +import mage.game.permanent.Permanent; +import mage.target.common.TargetLandPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class UltimaOriginOfOblivion extends CardImpl { + + public UltimaOriginOfOblivion(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.GOD); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever Ultima attacks, put a blight counter on target land. For as long as that land has a blight counter on it, it loses all land types and abilities and has "{T}: Add {C}." + Ability ability = new AttacksTriggeredAbility(new AddCountersTargetEffect(CounterType.BLIGHT.createInstance())); + ability.addEffect(new UltimaOriginOfOblivionEffect()); + ability.addTarget(new TargetLandPermanent()); + this.addAbility(ability); + + // Whenever you tap a land for {C}, add an additional {C}. + this.addAbility(new UltimaOriginOfOblivionTriggeredManaAbility()); + } + + private UltimaOriginOfOblivion(final UltimaOriginOfOblivion card) { + super(card); + } + + @Override + public UltimaOriginOfOblivion copy() { + return new UltimaOriginOfOblivion(this); + } +} + +class UltimaOriginOfOblivionEffect extends ContinuousEffectImpl { + + UltimaOriginOfOblivionEffect() { + super(Duration.Custom, Outcome.Benefit); + staticText = "For as long as that land has a blight counter on it, " + + "it loses all land types and abilities and has \"{T}: Add {C}.\""; + } + + private UltimaOriginOfOblivionEffect(final UltimaOriginOfOblivionEffect effect) { + super(effect); + } + + @Override + public UltimaOriginOfOblivionEffect copy() { + return new UltimaOriginOfOblivionEffect(this); + } + + @Override + public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent == null || !permanent.getCounters(game).containsKey(CounterType.BLIGHT)) { + discard(); + return false; + } + switch (layer) { + case TypeChangingEffects_4: + permanent.looseAllAbilities(game); + permanent.addAbility(new ColorlessManaAbility(), source.getSourceId(), game); + return true; + case AbilityAddingRemovingEffects_6: + permanent.removeAllSubTypes(game, SubTypeSet.NonBasicLandType); + permanent.removeAllSubTypes(game, SubTypeSet.BasicLandType); + return true; + default: + return false; + } + } + + @Override + public boolean hasLayer(Layer layer) { + switch (layer) { + case AbilityAddingRemovingEffects_6: + case TypeChangingEffects_4: + return true; + default: + return false; + } + } + + @Override + public boolean apply(Game game, Ability source) { + return false; + } +} + +class UltimaOriginOfOblivionTriggeredManaAbility extends TriggeredManaAbility { + + UltimaOriginOfOblivionTriggeredManaAbility() { + super(Zone.BATTLEFIELD, new BasicManaEffect(Mana.ColorlessMana(1))); + } + + private UltimaOriginOfOblivionTriggeredManaAbility(final UltimaOriginOfOblivionTriggeredManaAbility 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) { + TappedForManaEvent mEvent = (TappedForManaEvent) event; + Permanent permanent = mEvent.getPermanent(); + return permanent != null + && permanent.isLand(game) + && isControlledBy(event.getPlayerId()) + && mEvent.getMana().getColorless() > 0; + } + + @Override + public UltimaOriginOfOblivionTriggeredManaAbility copy() { + return new UltimaOriginOfOblivionTriggeredManaAbility(this); + } + + @Override + public String getRule() { + return "Whenever you tap a land for {C}, add an additional {C}."; + } +} diff --git a/Mage.Sets/src/mage/cards/u/UltimaWeapon.java b/Mage.Sets/src/mage/cards/u/UltimaWeapon.java new file mode 100644 index 00000000000..380e021f546 --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/UltimaWeapon.java @@ -0,0 +1,49 @@ +package mage.cards.u; + +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.effects.common.continuous.BoostEquippedEffect; +import mage.abilities.keyword.EquipAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.target.common.TargetOpponentsCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class UltimaWeapon extends CardImpl { + + public UltimaWeapon(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{7}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.EQUIPMENT); + + // Whenever equipped creature attacks, destroy target creature an opponent controls. + Ability ability = new AttacksTriggeredAbility(new DestroyTargetEffect()); + ability.addTarget(new TargetOpponentsCreaturePermanent()); + this.addAbility(ability); + + // Equipped creature gets +7/+7. + this.addAbility(new SimpleStaticAbility(new BoostEquippedEffect(7, 7))); + + // Equip {7} + this.addAbility(new EquipAbility(7)); + } + + private UltimaWeapon(final UltimaWeapon card) { + super(card); + } + + @Override + public UltimaWeapon copy() { + return new UltimaWeapon(this); + } +} diff --git a/Mage.Sets/src/mage/cards/u/UltimateMagicHoly.java b/Mage.Sets/src/mage/cards/u/UltimateMagicHoly.java new file mode 100644 index 00000000000..31538212fee --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/UltimateMagicHoly.java @@ -0,0 +1,68 @@ +package mage.cards.u; + +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.AddContinuousEffectToGame; +import mage.abilities.effects.common.PreventDamageToControllerEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.ForetellAbility; +import mage.abilities.keyword.IndestructibleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.stack.Spell; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class UltimateMagicHoly extends CardImpl { + + public UltimateMagicHoly(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{W}"); + + // Permanents you control gain indestructible until end of turn. If this spell was cast from exile, prevent all damage that would be dealt to you this turn. + this.getSpellAbility().addEffect(new GainAbilityControlledEffect( + IndestructibleAbility.getInstance(), Duration.EndOfTurn, StaticFilters.FILTER_PERMANENTS + )); + this.getSpellAbility().addEffect(new ConditionalOneShotEffect( + new AddContinuousEffectToGame(new PreventDamageToControllerEffect(Duration.EndOfTurn)), + UltimateMagicHolyCondition.instance, "If this spell was cast from exile, " + + "prevent all damage that would be dealt to you this turn" + )); + + // Foretell {2}{W} + this.addAbility(new ForetellAbility(this, "{2}{W}")); + } + + private UltimateMagicHoly(final UltimateMagicHoly card) { + super(card); + } + + @Override + public UltimateMagicHoly copy() { + return new UltimateMagicHoly(this); + } +} + +enum UltimateMagicHolyCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + return Optional + .ofNullable(source) + .map(Ability::getSourceId) + .map(game::getSpell) + .map(Spell::getFromZone) + .map(Zone.EXILED::match) + .orElse(false); + } +} diff --git a/Mage.Sets/src/mage/cards/u/UltimeciaOmnipotent.java b/Mage.Sets/src/mage/cards/u/UltimeciaOmnipotent.java new file mode 100644 index 00000000000..db51fd3f13d --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/UltimeciaOmnipotent.java @@ -0,0 +1,48 @@ +package mage.cards.u; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.effects.common.turn.AddExtraTurnControllerEffect; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.abilities.common.TransformIntoSourceTriggeredAbility; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; + +/** + * @author balazskristof + */ +public final class UltimeciaOmnipotent extends CardImpl { + + public UltimeciaOmnipotent(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.NIGHTMARE); + this.subtype.add(SubType.WARLOCK); + this.power = new MageInt(7); + this.toughness = new MageInt(7); + + this.color.setBlack(true); + this.color.setBlue(true); + + this.nightCard = true; + + // Menace + this.addAbility(new MenaceAbility()); + + // Time Compression -- When this creature transforms into Ultimecia, Omnipotent, take an extra turn after this one. + this.addAbility(new TransformIntoSourceTriggeredAbility(new AddExtraTurnControllerEffect()).withFlavorWord("Time Compression")); + } + + private UltimeciaOmnipotent(final UltimeciaOmnipotent card) { + super(card); + } + + @Override + public UltimeciaOmnipotent copy() { + return new UltimeciaOmnipotent(this); + } +} diff --git a/Mage.Sets/src/mage/cards/u/UltimeciaTemporalThreat.java b/Mage.Sets/src/mage/cards/u/UltimeciaTemporalThreat.java new file mode 100644 index 00000000000..ceec7075d46 --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/UltimeciaTemporalThreat.java @@ -0,0 +1,53 @@ +package mage.cards.u; + +import mage.MageInt; +import mage.abilities.common.DealsDamageToAPlayerAllTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.TapAllEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SetTargetPointer; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class UltimeciaTemporalThreat extends CardImpl { + + public UltimeciaTemporalThreat(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{U}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARLOCK); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // When Ultimecia enters, tap all creatures your opponents control. + this.addAbility(new EntersBattlefieldTriggeredAbility( + new TapAllEffect(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURES) + )); + + // Whenever a creature you control deals combat damage to a player, draw a card. + this.addAbility(new DealsDamageToAPlayerAllTriggeredAbility( + new DrawCardSourceControllerEffect(1), + StaticFilters.FILTER_CONTROLLED_A_CREATURE, + false, SetTargetPointer.NONE, true + )); + } + + private UltimeciaTemporalThreat(final UltimeciaTemporalThreat card) { + super(card); + } + + @Override + public UltimeciaTemporalThreat copy() { + return new UltimeciaTemporalThreat(this); + } +} diff --git a/Mage.Sets/src/mage/cards/u/UltimeciaTimeSorceress.java b/Mage.Sets/src/mage/cards/u/UltimeciaTimeSorceress.java new file mode 100644 index 00000000000..539ac995609 --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/UltimeciaTimeSorceress.java @@ -0,0 +1,60 @@ +package mage.cards.u; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility; +import mage.abilities.costs.CompositeCost; +import mage.abilities.costs.common.ExileFromGraveCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.effects.keyword.SurveilEffect; +import mage.abilities.keyword.TransformAbility; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.target.common.TargetCardInYourGraveyard; + +/** + * @author balazskristof + */ +public final class UltimeciaTimeSorceress extends CardImpl { + + public UltimeciaTimeSorceress(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARLOCK); + this.power = new MageInt(4); + this.toughness = new MageInt(5); + + this.secondSideCardClazz = mage.cards.u.UltimeciaOmnipotent.class; + + // Whenever Ultimecia enters or attacks, surveil 2. + this.addAbility(new EntersBattlefieldOrAttacksSourceTriggeredAbility(new SurveilEffect(2))); + + // At the beginning of your end step, you may pay {4}{U}{U}{B}{B} and exile eight cards from your graveyard. If you do, transform Ultimecia. + this.addAbility(new BeginningOfEndStepTriggeredAbility(new DoIfCostPaid( + new TransformSourceEffect(), + new CompositeCost( + new ManaCostsImpl<>("{4}{U}{U}{B}{B}"), + new ExileFromGraveCost(new TargetCardInYourGraveyard(8)), + "{4}{U}{U}{B}{B} and exile eight cards from your graveyard" + ) + ))); + this.addAbility(new TransformAbility()); + } + + private UltimeciaTimeSorceress(final UltimeciaTimeSorceress card) { + super(card); + } + + @Override + public UltimeciaTimeSorceress copy() { + return new UltimeciaTimeSorceress(this); + } +} diff --git a/Mage.Sets/src/mage/cards/u/UmaroRagingYeti.java b/Mage.Sets/src/mage/cards/u/UmaroRagingYeti.java new file mode 100644 index 00000000000..65802796d48 --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/UmaroRagingYeti.java @@ -0,0 +1,69 @@ +package mage.cards.u; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.effects.common.discard.DiscardHandControllerEffect; +import mage.abilities.keyword.TrampleAbility; +import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.StaticFilters; +import mage.target.common.TargetAnyTarget; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class UmaroRagingYeti extends CardImpl { + + public UmaroRagingYeti(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.YETI); + this.subtype.add(SubType.BERSERKER); + this.power = new MageInt(6); + this.toughness = new MageInt(6); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // At the beginning of combat on your turn, choose one at random -- + // * Other creatures you control get +3/+0 and gain trample until end of turn. + Ability ability = new BeginningOfCombatTriggeredAbility(new BoostControlledEffect( + 3, 0, Duration.EndOfTurn, true + )); + ability.addEffect(new GainAbilityControlledEffect( + TrampleAbility.getInstance(), Duration.EndOfTurn, + StaticFilters.FILTER_PERMANENT_CREATURE, true + ).setText("and gain trample until end of turn")); + ability.getModes().setRandom(true); + + // * Discard your hand, then draw four cards. + ability.addMode(new Mode(new DiscardHandControllerEffect()) + .addEffect(new DrawCardSourceControllerEffect(4).concatBy(", then"))); + + // * Umaro deals 5 damage to any target. + ability.addMode(new Mode(new DamageTargetEffect(5)).addTarget(new TargetAnyTarget())); + this.addAbility(ability); + } + + private UmaroRagingYeti(final UmaroRagingYeti card) { + super(card); + } + + @Override + public UmaroRagingYeti copy() { + return new UmaroRagingYeti(this); + } +} diff --git a/Mage.Sets/src/mage/cards/u/UnableToScream.java b/Mage.Sets/src/mage/cards/u/UnableToScream.java index 215f870b1bd..301be95a9b1 100644 --- a/Mage.Sets/src/mage/cards/u/UnableToScream.java +++ b/Mage.Sets/src/mage/cards/u/UnableToScream.java @@ -7,7 +7,7 @@ import mage.abilities.effects.common.AttachEffect; import mage.abilities.effects.common.continuous.AddCardSubtypeAttachedEffect; import mage.abilities.effects.common.continuous.AddCardTypeAttachedEffect; import mage.abilities.effects.common.continuous.LoseAllAbilitiesAttachedEffect; -import mage.abilities.effects.common.continuous.SetBasePowerToughnessEnchantedEffect; +import mage.abilities.effects.common.continuous.SetBasePowerToughnessAttachedEffect; import mage.abilities.keyword.EnchantAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -40,7 +40,7 @@ public final class UnableToScream extends CardImpl { Ability ability = new SimpleStaticAbility(new LoseAllAbilitiesAttachedEffect(AttachmentType.AURA)); ability.addEffect(new AddCardSubtypeAttachedEffect(SubType.TOY, AttachmentType.AURA).setText(" and is a Toy")); ability.addEffect(new AddCardTypeAttachedEffect(CardType.ARTIFACT, AttachmentType.AURA).setText(" artifact creature")); - ability.addEffect(new SetBasePowerToughnessEnchantedEffect(0, 2).setText(" with base power and toughness 0/2 in addition to its other types.")); + ability.addEffect(new SetBasePowerToughnessAttachedEffect(0, 2, AttachmentType.AURA).setText(" with base power and toughness 0/2 in addition to its other types.")); this.addAbility(ability); // As long as enchanted creature is face down, it can't be turned face up. diff --git a/Mage.Sets/src/mage/cards/u/UnchartedHaven.java b/Mage.Sets/src/mage/cards/u/UnchartedHaven.java index 305a829ef75..dc366f40160 100644 --- a/Mage.Sets/src/mage/cards/u/UnchartedHaven.java +++ b/Mage.Sets/src/mage/cards/u/UnchartedHaven.java @@ -1,15 +1,12 @@ package mage.cards.u; -import mage.abilities.common.AsEntersBattlefieldAbility; -import mage.abilities.common.EntersBattlefieldTappedAbility; +import mage.abilities.common.EntersBattlefieldTappedAsItEntersChooseColorAbility; import mage.abilities.costs.common.TapSourceCost; -import mage.abilities.effects.common.ChooseColorEffect; import mage.abilities.effects.mana.AddManaChosenColorEffect; import mage.abilities.mana.SimpleManaAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; import mage.constants.Zone; import java.util.UUID; @@ -23,10 +20,8 @@ public final class UnchartedHaven extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); // Uncharted Haven enters the battlefield tapped. - this.addAbility(new EntersBattlefieldTappedAbility()); - // As Uncharted Haven enters the battlefield, choose a color. - this.addAbility(new AsEntersBattlefieldAbility(new ChooseColorEffect(Outcome.Neutral))); + this.addAbility(new EntersBattlefieldTappedAsItEntersChooseColorAbility()); // {T}: Add one mana of the chosen color. this.addAbility(new SimpleManaAbility(Zone.BATTLEFIELD, new AddManaChosenColorEffect(), new TapSourceCost())); diff --git a/Mage.Sets/src/mage/cards/u/UndercityDireRat.java b/Mage.Sets/src/mage/cards/u/UndercityDireRat.java new file mode 100644 index 00000000000..17e11359e0c --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/UndercityDireRat.java @@ -0,0 +1,38 @@ +package mage.cards.u; + +import mage.MageInt; +import mage.abilities.common.DiesSourceTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.game.permanent.token.TreasureToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class UndercityDireRat extends CardImpl { + + public UndercityDireRat(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}"); + + this.subtype.add(SubType.RAT); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Rat Tail -- When this creature dies, create a Treasure token. + this.addAbility(new DiesSourceTriggeredAbility(new CreateTokenEffect(new TreasureToken())).withFlavorWord("Rat Tail")); + } + + private UndercityDireRat(final UndercityDireRat card) { + super(card); + } + + @Override + public UndercityDireRat copy() { + return new UndercityDireRat(this); + } +} diff --git a/Mage.Sets/src/mage/cards/u/UnderfootUnderdogs.java b/Mage.Sets/src/mage/cards/u/UnderfootUnderdogs.java index 6c57550fe6b..94622b6c05f 100644 --- a/Mage.Sets/src/mage/cards/u/UnderfootUnderdogs.java +++ b/Mage.Sets/src/mage/cards/u/UnderfootUnderdogs.java @@ -4,6 +4,7 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.combat.CantBeBlockedTargetEffect; @@ -45,6 +46,7 @@ public final class UnderfootUnderdogs extends CardImpl { // {1}, {T}: Target creature you control with power 2 or less can't be blocked this turn. Ability ability = new SimpleActivatedAbility(new CantBeBlockedTargetEffect(), new GenericManaCost(1)); + ability.addCost(new TapSourceCost()); ability.addTarget(new TargetPermanent(filter)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/u/UnscytheKillerOfKings.java b/Mage.Sets/src/mage/cards/u/UnscytheKillerOfKings.java index 52cad886320..4542fe7a200 100644 --- a/Mage.Sets/src/mage/cards/u/UnscytheKillerOfKings.java +++ b/Mage.Sets/src/mage/cards/u/UnscytheKillerOfKings.java @@ -13,6 +13,7 @@ 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.token.ZombieToken; import mage.players.Player; @@ -38,7 +39,8 @@ public final class UnscytheKillerOfKings extends CardImpl { this.addAbility(ability); // Whenever a creature dealt damage by equipped creature this turn dies, you may exile that card. If you do, create a 2/2 black Zombie creature token. - this.addAbility(new DealtDamageAttachedAndDiedTriggeredAbility(new UnscytheEffect(), true)); + this.addAbility(new DealtDamageAttachedAndDiedTriggeredAbility(new UnscytheEffect(), true, + StaticFilters.FILTER_PERMANENT_CREATURE, SetTargetPointer.CARD, AttachmentType.EQUIPMENT)); // Equip {2} this.addAbility(new EquipAbility(Outcome.BoostCreature, new GenericManaCost(2), new TargetControlledCreaturePermanent(), false)); diff --git a/Mage.Sets/src/mage/cards/v/ValgavothTerrorEater.java b/Mage.Sets/src/mage/cards/v/ValgavothTerrorEater.java new file mode 100644 index 00000000000..f24eae3c3bc --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/ValgavothTerrorEater.java @@ -0,0 +1,221 @@ +package mage.cards.v; + +import mage.MageIdentifier; +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +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.effects.AsThoughEffectImpl; +import mage.abilities.effects.common.replacement.GraveyardFromAnywhereExileReplacementEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.LifelinkAbility; +import mage.abilities.keyword.WardAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.CardWithHalves; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.game.ExileZone; +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.util.CardUtil; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class ValgavothTerrorEater extends CardImpl { + + public ValgavothTerrorEater(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{6}{B}{B}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.ELDER); + this.subtype.add(SubType.DEMON); + this.power = new MageInt(9); + this.toughness = new MageInt(9); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // Ward--Sacrifice three nonland permanents. + this.addAbility(new WardAbility(new SacrificeTargetCost(3, StaticFilters.FILTER_PERMANENTS_NON_LAND))); + + // If a card you didn't control would be put into an opponent's graveyard from anywhere, exile it instead. + this.addAbility(new SimpleStaticAbility(new ValgavothTerrorEaterReplacementEffect())); + + // During your turn, you may play cards exiled with Valgavoth. If you cast a spell this way, pay life equal to its mana value rather than pay its mana cost. + this.addAbility(new SimpleStaticAbility(new ValgavothTerrorEaterPlayEffect()).setIdentifier(MageIdentifier.ValgavothTerrorEaterAlternateCast)); + } + + private ValgavothTerrorEater(final ValgavothTerrorEater card) { + super(card); + } + + @Override + public ValgavothTerrorEater copy() { + return new ValgavothTerrorEater(this); + } +} + +class ValgavothTerrorEaterReplacementEffect extends GraveyardFromAnywhereExileReplacementEffect { + + ValgavothTerrorEaterReplacementEffect() { + super(StaticFilters.FILTER_CARD_A, false); + staticText = "If a card you didn't control would be put into an opponent's graveyard from anywhere, exile it instead"; + } + + private ValgavothTerrorEaterReplacementEffect(final ValgavothTerrorEaterReplacementEffect effect) { + super(effect); + } + + @Override + public ValgavothTerrorEaterReplacementEffect copy() { + return new ValgavothTerrorEaterReplacementEffect(this); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + + // TODO: part of #13594 refactor to replaceEvent (add exile zone info in ZoneChangeEvent?) + Permanent permanent = game.getPermanent(event.getTargetId()); + if (permanent != null) { + return controller.moveCardsToExile( + permanent, source, game, true, + CardUtil.getCardExileZoneId(game, source), + CardUtil.createObjectRelatedWindowTitle(source, game, "(may be played using life)") + ); + } + + Card card = game.getCard(event.getTargetId()); + if (card != null) { + return controller.moveCardsToExile( + card, source, game, true, + CardUtil.getCardExileZoneId(game, source), + CardUtil.createObjectRelatedWindowTitle(source, game, "(may be played using life)")); + } + + return false; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + // checks that zce movement is to graveyard + if (!super.applies(event, source, game)) { + return false; + } + + ZoneChangeEvent zce = (ZoneChangeEvent) event; + UUID controllerId = source.getControllerId(); + Player controller = game.getPlayer(controllerId); + if (controller == null) { + return false; + } + + Card card = game.getCard(event.getTargetId()); + if (card == null) { + return false; + } + + if (zce.getFromZone() == Zone.BATTLEFIELD) { + Permanent permanent = zce.getTarget(); + // is the permanent being moved controlled by someone that's not you? + if (permanent == null || permanent.getControllerId().equals(controllerId)) { + return false; + } + } else if (zce.getFromZone() == Zone.STACK) { + Spell spell = game.getSpellOrLKIStack(event.getTargetId()); + if (spell == null) { + // there is no direct link between moving a split/mdfc card and the stack part that was cast. + // so we try them both and see if we find anything. + if (card instanceof CardWithHalves) { + CardWithHalves cwh = (CardWithHalves) card; + spell = game.getSpellOrLKIStack(cwh.getLeftHalfCard().getId()); + if (spell == null) { + spell = game.getSpellOrLKIStack(cwh.getRightHalfCard().getId()); + } + } + } + // is the spell being moved controlled by someone that's not you? + if (spell == null || spell.getControllerId().equals(controllerId)) { + return false; + } + } + + // is the card going to an opponent's graveyard? + return controller.hasOpponent(card.getOwnerId(), game); + } +} + +class ValgavothTerrorEaterPlayEffect extends AsThoughEffectImpl { + + ValgavothTerrorEaterPlayEffect() { + super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.AIDontUseIt); // AI will need help with this + staticText = "during your turn, you may play cards exiled with {this}. If you cast a spell this way, pay life equal to its mana value rather than pay its mana cost"; + } + + private ValgavothTerrorEaterPlayEffect(final ValgavothTerrorEaterPlayEffect effect) { + super(effect); + } + + @Override + public ValgavothTerrorEaterPlayEffect copy() { + return new ValgavothTerrorEaterPlayEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + if (!source.isControlledBy(affectedControllerId) || !game.isActivePlayer(affectedControllerId)) { + return false; + } + Player player = game.getPlayer(affectedControllerId); + if (player == null) { + return false; + } + + Card card = game.getCard(objectId); + if (card == null) { + return false; + } + UUID mainId = card.getMainCard().getId(); // for split cards + + MageObject sourceObject = source.getSourceObject(game); + UUID exileZoneId = CardUtil.getExileZoneId(game, sourceObject.getId(), sourceObject.getZoneChangeCounter(game)); + ExileZone exileZone = game.getExile().getExileZone(exileZoneId); + if (exileZone == null || !exileZone.contains(mainId)) { + return false; + } + + // allows to play/cast with alternative life cost + if (!card.isLand(game)) { + PayLifeCost lifeCost = new PayLifeCost(card.getSpellAbility().getManaCosts().manaValue()); + Costs newCosts = new CostsImpl(); + newCosts.add(lifeCost); + newCosts.addAll(card.getSpellAbility().getCosts()); + player.setCastSourceIdWithAlternateMana(card.getId(), null, newCosts, MageIdentifier.ValgavothTerrorEaterAlternateCast); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/v/ValkyrieAerialUnit.java b/Mage.Sets/src/mage/cards/v/ValkyrieAerialUnit.java new file mode 100644 index 00000000000..4565d07196a --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/ValkyrieAerialUnit.java @@ -0,0 +1,45 @@ +package mage.cards.v; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.keyword.SurveilEffect; +import mage.abilities.keyword.AffinityForArtifactsAbility; +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 ValkyrieAerialUnit extends CardImpl { + + public ValkyrieAerialUnit(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{5}{U}{U}"); + + this.subtype.add(SubType.CONSTRUCT); + this.power = new MageInt(5); + this.toughness = new MageInt(4); + + // Affinity for artifacts + this.addAbility(new AffinityForArtifactsAbility()); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When this creature enters, surveil 2. + this.addAbility(new EntersBattlefieldTriggeredAbility(new SurveilEffect(2))); + } + + private ValkyrieAerialUnit(final ValkyrieAerialUnit card) { + super(card); + } + + @Override + public ValkyrieAerialUnit copy() { + return new ValkyrieAerialUnit(this); + } +} diff --git a/Mage.Sets/src/mage/cards/v/VanilleCheerfulLCie.java b/Mage.Sets/src/mage/cards/v/VanilleCheerfulLCie.java new file mode 100644 index 00000000000..a3af63ac93b --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VanilleCheerfulLCie.java @@ -0,0 +1,95 @@ +package mage.cards.v; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.MeldCondition; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.MeldEffect; +import mage.abilities.effects.common.MillCardsControllerEffect; +import mage.abilities.triggers.BeginningOfFirstMainTriggeredAbility; +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.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class VanilleCheerfulLCie extends CardImpl { + + private static final Condition condition = new MeldCondition("Fang, Fearless l'Cie"); + + public VanilleCheerfulLCie(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.CLERIC); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + this.meldsWithClazz = mage.cards.f.FangFearlessLCie.class; + this.meldsToClazz = mage.cards.r.RagnarokDivineDeliverance.class; + + // When Vanille enters, mill two cards, then return a permanent card from your graveyard to your hand. + Ability ability = new EntersBattlefieldTriggeredAbility(new MillCardsControllerEffect(2)); + ability.addEffect(new VanilleCheerfulLCieEffect()); + this.addAbility(ability); + + // At the beginning of your first main phase, if you both own and control Vanille and a creature named Fang, Fearless l'Cie, you may pay {3}{B}{G}. If you do, exile them, then meld them into Ragnarok, Divine Deliverance. + this.addAbility(new BeginningOfFirstMainTriggeredAbility(new DoIfCostPaid( + new MeldEffect("Fang, Fearless l'Cie", "Ragnarok, Divine Deliverance") + .setText("exile them, then meld them into Ragnarok, Divine Deliverance"), + new ManaCostsImpl<>("{3}{B}{G}") + )).withInterveningIf(condition)); + } + + private VanilleCheerfulLCie(final VanilleCheerfulLCie card) { + super(card); + } + + @Override + public VanilleCheerfulLCie copy() { + return new VanilleCheerfulLCie(this); + } +} + +class VanilleCheerfulLCieEffect extends OneShotEffect { + + VanilleCheerfulLCieEffect() { + super(Outcome.Benefit); + staticText = ", then return a permanent card from your graveyard to your hand"; + } + + private VanilleCheerfulLCieEffect(final VanilleCheerfulLCieEffect effect) { + super(effect); + } + + @Override + public VanilleCheerfulLCieEffect copy() { + return new VanilleCheerfulLCieEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null || player.getGraveyard().count(StaticFilters.FILTER_CARD_PERMANENT, game) < 1) { + return false; + } + TargetCard target = new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_PERMANENT); + target.withNotTarget(true); + player.choose(outcome, player.getGraveyard(), target, source, game); + Card card = game.getCard(target.getFirstTarget()); + return card != null && player.moveCards(card, Zone.HAND, source, game); + } +} diff --git a/Mage.Sets/src/mage/cards/v/Vault112SadisticSimulation.java b/Mage.Sets/src/mage/cards/v/Vault112SadisticSimulation.java index 60292d91034..02267f60dbf 100644 --- a/Mage.Sets/src/mage/cards/v/Vault112SadisticSimulation.java +++ b/Mage.Sets/src/mage/cards/v/Vault112SadisticSimulation.java @@ -92,7 +92,7 @@ class Vault112SadisticSimulationChapterEffect extends OneShotEffect { } int numberToPay = controller.getAmount( 0, controller.getCountersCount(CounterType.ENERGY), - "How many {E} do you like to pay?", game + "How many {E} do you like to pay?", source, game ); if (numberToPay <= 0) { return true; diff --git a/Mage.Sets/src/mage/cards/v/Vault11VotersDilemma.java b/Mage.Sets/src/mage/cards/v/Vault11VotersDilemma.java new file mode 100644 index 00000000000..68159f35e84 --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/Vault11VotersDilemma.java @@ -0,0 +1,136 @@ +package mage.cards.v; + +import mage.abilities.Ability; +import mage.abilities.common.SagaAbility; +import mage.abilities.dynamicvalue.common.OpponentsCount; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.choices.VoteHandler; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SagaChapter; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.HumanSoldierToken; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; + +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * @author TheElk801 + */ +public final class Vault11VotersDilemma extends CardImpl { + + public Vault11VotersDilemma(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{W}{B}"); + + this.subtype.add(SubType.SAGA); + + // (As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.) + SagaAbility sagaAbility = new SagaAbility(this); + + // I -- For each opponent, you create a 1/1 white Human Soldier creature token. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_I, + new CreateTokenEffect(new HumanSoldierToken(), OpponentsCount.instance) + .setText("for each opponent, you create a 1/1 white Human Soldier creature token") + ); + + // II, III -- Each player secretly votes for up to one creature, then those votes are revealed. If no creature got votes, each player draws a card. Otherwise, destroy each creature with the most votes or tied for most votes. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_II, SagaChapter.CHAPTER_III, new Vault11VotersDilemmaEffect() + ); + this.addAbility(sagaAbility); + } + + private Vault11VotersDilemma(final Vault11VotersDilemma card) { + super(card); + } + + @Override + public Vault11VotersDilemma copy() { + return new Vault11VotersDilemma(this); + } +} + +class Vault11VotersDilemmaEffect extends OneShotEffect { + + Vault11VotersDilemmaEffect() { + super(Outcome.Benefit); + staticText = "each player secretly votes for up to one creature, then those votes are revealed. " + + "If no creature got votes, each player draws a card. Otherwise, " + + "destroy each creature with the most votes or tied for most votes"; + } + + private Vault11VotersDilemmaEffect(final Vault11VotersDilemmaEffect effect) { + super(effect); + } + + @Override + public Vault11VotersDilemmaEffect copy() { + return new Vault11VotersDilemmaEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Vault11VotersDilemmaVote vote = new Vault11VotersDilemmaVote(); + vote.doVotes(source, game); + Set mostVoted = vote.getMostVoted(); + mostVoted.removeIf(Objects::isNull); + if (!mostVoted.isEmpty()) { + for (Permanent permanent : mostVoted) { + permanent.destroy(source, game); + } + return true; + } + for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { + Optional.ofNullable(game.getPlayer(playerId)) + .map(player -> player.drawCards(1, source, game)); + } + return true; + } +} + +class Vault11VotersDilemmaVote extends VoteHandler { + + Vault11VotersDilemmaVote() { + super(); + this.secret = true; + } + + @Override + protected Set getPossibleVotes(Ability source, Game game) { + return game + .getBattlefield() + .getActivePermanents( + StaticFilters.FILTER_PERMANENT_CREATURE, + source.getControllerId(), source, game + ) + .stream() + .collect(Collectors.toSet()); + } + + @Override + protected Permanent playerChoose(String voteInfo, Player player, Player decidingPlayer, Ability source, Game game) { + TargetPermanent target = new TargetCreaturePermanent(0, 1); + target.withChooseHint(voteInfo + " (to exile)"); + target.withNotTarget(true); + decidingPlayer.choose(Outcome.Exile, target, source, game); + return game.getPermanent(target.getFirstTarget()); + } + + @Override + protected String voteName(Permanent vote) { + return vote.getIdName(); + } +} diff --git a/Mage.Sets/src/mage/cards/v/Vault13DwellersJourney.java b/Mage.Sets/src/mage/cards/v/Vault13DwellersJourney.java new file mode 100644 index 00000000000..84ccfe61af4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/Vault13DwellersJourney.java @@ -0,0 +1,132 @@ +package mage.cards.v; + +import mage.abilities.Ability; +import mage.abilities.common.SagaAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileUntilSourceLeavesEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.keyword.ScryEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.TargetPermanent; +import mage.target.common.TargetCardInExile; +import mage.target.targetadjustment.ForEachOpponentTargetsAdjuster; +import mage.util.CardUtil; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Vault13DwellersJourney extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("enchantment or creature"); + + static { + filter.add(AnotherPredicate.instance); + filter.add(Predicates.or( + CardType.ENCHANTMENT.getPredicate(), + CardType.CREATURE.getPredicate() + )); + } + + public Vault13DwellersJourney(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{W}"); + + this.subtype.add(SubType.SAGA); + + // (As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.) + SagaAbility sagaAbility = new SagaAbility(this); + + // I -- For each player, exile up to one other target enchantment or creature that player controls until Vault 13 leaves the battlefield. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_I, + triggeredAbility -> { + triggeredAbility.addEffect(new ExileUntilSourceLeavesEffect() + .setText("for each player, exile up to one other target enchantment or " + + "creature that player controls until {this} leaves the battlefield")); + triggeredAbility.addTarget(new TargetPermanent(filter)); + triggeredAbility.setTargetAdjuster(new ForEachOpponentTargetsAdjuster()); + } + ); + + // II -- You gain 2 life and scry 2. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_II, new GainLifeEffect(2), + new ScryEffect(2, false).concatBy("and") + ); + + // III -- Return two cards exiled with Vault 13 to the battlefield under their owners' control and put the rest on the bottom of their owners' libraries. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_III, new Vault13DwellersJourneyEffect()); + this.addAbility(sagaAbility); + } + + private Vault13DwellersJourney(final Vault13DwellersJourney card) { + super(card); + } + + @Override + public Vault13DwellersJourney copy() { + return new Vault13DwellersJourney(this); + } +} + +class Vault13DwellersJourneyEffect extends OneShotEffect { + + Vault13DwellersJourneyEffect() { + super(Outcome.Benefit); + staticText = "return two cards exiled with {this} to the battlefield " + + "under their owners' control and put the rest on the bottom of their owners' libraries"; + } + + private Vault13DwellersJourneyEffect(final Vault13DwellersJourneyEffect effect) { + super(effect); + } + + @Override + public Vault13DwellersJourneyEffect copy() { + return new Vault13DwellersJourneyEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Cards cards = new CardsImpl(); + Optional.ofNullable(game.getExile().getExileZone(CardUtil.getExileZoneId(game, source))) + .ifPresent(cards::addAll); + if (player == null || cards.isEmpty()) { + return false; + } + Cards toPlay = new CardsImpl(); + if (cards.size() > 2) { + TargetCard target = new TargetCardInExile(2, 2, StaticFilters.FILTER_CARD_CARDS); + target.withNotTarget(true); + target.withChooseHint("to return to the battlefield"); + player.choose(outcome, cards, target, source, game); + toPlay.addAll(target.getTargets()); + } else { + toPlay.addAll(cards); + } + toPlay.retainZone(Zone.EXILED, game); + if (!toPlay.isEmpty()) { + player.moveCards( + toPlay.getCards(game), Zone.BATTLEFIELD, source, game, + false, false, true, null + ); + cards.retainZone(Zone.EXILED, game); + } + player.putCardsOnBottomOfLibrary(cards, game, source, true); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/v/VeilOfSummer.java b/Mage.Sets/src/mage/cards/v/VeilOfSummer.java index a586809ab14..fa2f3e29a4c 100644 --- a/Mage.Sets/src/mage/cards/v/VeilOfSummer.java +++ b/Mage.Sets/src/mage/cards/v/VeilOfSummer.java @@ -120,10 +120,12 @@ class VeilOfSummerEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { game.addEffect(new GainAbilityControlledEffect( - HexproofFromBlueAbility.getInstance(), Duration.EndOfTurn + HexproofFromBlueAbility.getInstance(), Duration.EndOfTurn, + StaticFilters.FILTER_PERMANENTS ), source); game.addEffect(new GainAbilityControlledEffect( - HexproofFromBlackAbility.getInstance(), Duration.EndOfTurn + HexproofFromBlackAbility.getInstance(), Duration.EndOfTurn, + StaticFilters.FILTER_PERMANENTS ), source); game.addEffect(new GainAbilityControllerEffect( HexproofFromBlueAbility.getInstance(), Duration.EndOfTurn @@ -133,4 +135,4 @@ class VeilOfSummerEffect extends OneShotEffect { ), source); return true; } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/v/VengefulRebel.java b/Mage.Sets/src/mage/cards/v/VengefulRebel.java index 32df6c4df8b..272d2798277 100644 --- a/Mage.Sets/src/mage/cards/v/VengefulRebel.java +++ b/Mage.Sets/src/mage/cards/v/VengefulRebel.java @@ -1,24 +1,22 @@ package mage.cards.v; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.condition.common.RevoltCondition; -import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.common.continuous.BoostTargetEffect; 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.target.common.TargetOpponentsCreaturePermanent; import mage.watchers.common.RevoltWatcher; +import java.util.UUID; + /** - * * @author fireshoes */ public final class VengefulRebel extends CardImpl { @@ -33,12 +31,8 @@ public final class VengefulRebel extends CardImpl { // Revolt — When Vengeful Rebel enters the battlefield, if a permanent you controlled left the battlefield this turn, // target creature an opponent controls gets -3/-3 until end of turn. - Ability ability = new ConditionalInterveningIfTriggeredAbility( - new EntersBattlefieldTriggeredAbility(new BoostTargetEffect(-3, -3, Duration.EndOfTurn), false), - RevoltCondition.instance, - "When {this} enters, if a permanent you controlled left the battlefield this turn, " - + "target creature an opponent controls gets -3/-3 until end of turn" - ); + Ability ability = new EntersBattlefieldTriggeredAbility(new BoostTargetEffect(-3, -3)) + .withInterveningIf(RevoltCondition.instance); ability.addTarget(new TargetOpponentsCreaturePermanent()); ability.setAbilityWord(AbilityWord.REVOLT); this.addAbility(ability.addHint(RevoltCondition.getHint()), new RevoltWatcher()); diff --git a/Mage.Sets/src/mage/cards/v/VexingBauble.java b/Mage.Sets/src/mage/cards/v/VexingBauble.java index 276328d321d..bfa2e028493 100644 --- a/Mage.Sets/src/mage/cards/v/VexingBauble.java +++ b/Mage.Sets/src/mage/cards/v/VexingBauble.java @@ -1,10 +1,8 @@ package mage.cards.v; -import java.util.UUID; - import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; -import mage.abilities.common.SpellCastNoManaSpentTriggeredAbility; +import mage.abilities.common.SpellCastAllTriggeredAbility; import mage.abilities.costs.common.SacrificeSourceCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.GenericManaCost; @@ -13,20 +11,23 @@ import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Zone; +import mage.constants.SetTargetPointer; +import mage.filter.StaticFilters; + +import java.util.UUID; /** - * * @author grimreap124 */ public final class VexingBauble extends CardImpl { public VexingBauble(UUID ownerId, CardSetInfo setInfo) { - super(ownerId, setInfo, new CardType[] { CardType.ARTIFACT }, "{1}"); + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}"); // Whenever a player casts a spell, if no mana was spent to cast it, counter that spell. - this.addAbility(new SpellCastNoManaSpentTriggeredAbility( - new CounterTargetEffect().setText("counter that spell"))); + this.addAbility(new SpellCastAllTriggeredAbility( + new CounterTargetEffect(), StaticFilters.FILTER_SPELL_NO_MANA_SPENT, false, SetTargetPointer.SPELL + )); // {1}, {T}, Sacrifice Vexing Bauble: Draw a card. Ability ability = new SimpleActivatedAbility(new DrawCardSourceControllerEffect(1), @@ -34,7 +35,6 @@ public final class VexingBauble extends CardImpl { ability.addCost(new TapSourceCost()); ability.addCost(new SacrificeSourceCost()); this.addAbility(ability); - ; } private VexingBauble(final VexingBauble card) { diff --git a/Mage.Sets/src/mage/cards/v/VigilForTheLost.java b/Mage.Sets/src/mage/cards/v/VigilForTheLost.java index b1dea4e1a2c..5350f2c0c15 100644 --- a/Mage.Sets/src/mage/cards/v/VigilForTheLost.java +++ b/Mage.Sets/src/mage/cards/v/VigilForTheLost.java @@ -53,7 +53,7 @@ class VigilForTheLostEffect extends OneShotEffect { if (controller == null) { return false; } - int costX = controller.announceXMana(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source); + int costX = controller.announceX(0, Integer.MAX_VALUE, "Announce the value for {X} (pay to gain life)", game, source, true); if (new GenericManaCost(costX).pay(source, game, source, source.getControllerId(), false, null)) { controller.gainLife(costX, game, source); return true; diff --git a/Mage.Sets/src/mage/cards/v/VincentValentine.java b/Mage.Sets/src/mage/cards/v/VincentValentine.java new file mode 100644 index 00000000000..6cbb8596dd6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VincentValentine.java @@ -0,0 +1,88 @@ +package mage.cards.v; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.DiesCreatureTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.TransformAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class VincentValentine extends CardImpl { + + public VincentValentine(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.ASSASSIN); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + this.secondSideCardClazz = mage.cards.g.GalianBeast.class; + + // Whenever a creature an opponent controls dies, put a number of +1/+1 counters on Vincent Valentine equal to that creature's power. + this.addAbility(new DiesCreatureTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance(), VincentValentineValue.instance) + .setText("put a number of +1/+1 counters on {this} equal to that creature's power"), + false, StaticFilters.FILTER_OPPONENTS_PERMANENT_A_CREATURE + )); + + // Whenever Vincent Valentine attacks, you may transform it. + this.addAbility(new TransformAbility()); + this.addAbility(new AttacksTriggeredAbility(new TransformSourceEffect(), true)); + } + + private VincentValentine(final VincentValentine card) { + super(card); + } + + @Override + public VincentValentine copy() { + return new VincentValentine(this); + } +} + +enum VincentValentineValue implements DynamicValue { + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return Optional + .of((Permanent) effect.getValue("creatureDied")) + .map(MageObject::getPower) + .map(MageInt::getValue) + .orElse(0); + } + + @Override + public VincentValentineValue copy() { + return this; + } + + @Override + public String getMessage() { + return ""; + } + + @Override + public String toString() { + return "1"; + } +} diff --git a/Mage.Sets/src/mage/cards/v/VincentVengefulAtoner.java b/Mage.Sets/src/mage/cards/v/VincentVengefulAtoner.java new file mode 100644 index 00000000000..9667d82c503 --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VincentVengefulAtoner.java @@ -0,0 +1,98 @@ +package mage.cards.v; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.DealsDamageToOpponentTriggeredAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +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.Outcome; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.game.Game; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class VincentVengefulAtoner extends CardImpl { + + public VincentVengefulAtoner(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.ASSASSIN); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Menace + this.addAbility(new MenaceAbility()); + + // Whenever one or more creatures you control deal combat damage to a player, put a +1/+1 counter on Vincent. + this.addAbility(new OneOrMoreCombatDamagePlayerTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()) + )); + + // Chaos -- Whenever Vincent deals combat damage to an opponent, it deals that much damage to each other opponent if Vincent's power is 7 or greater. + this.addAbility(new DealsDamageToOpponentTriggeredAbility( + new VincentVengefulAtonerEffect(), false, true, true + )); + } + + private VincentVengefulAtoner(final VincentVengefulAtoner card) { + super(card); + } + + @Override + public VincentVengefulAtoner copy() { + return new VincentVengefulAtoner(this); + } +} + +class VincentVengefulAtonerEffect extends OneShotEffect { + + VincentVengefulAtonerEffect() { + super(Outcome.Benefit); + staticText = "it deals that much damage to each other opponent if {this}'s power is 7 or greater"; + } + + private VincentVengefulAtonerEffect(final VincentVengefulAtonerEffect effect) { + super(effect); + } + + @Override + public VincentVengefulAtonerEffect copy() { + return new VincentVengefulAtonerEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + if (Optional + .ofNullable(source.getSourcePermanentOrLKI(game)) + .map(MageObject::getPower) + .map(MageInt::getValue) + .orElse(0) < 7) { + return false; + } + int amount = (Integer) getValue("damage"); + if (amount < 1) { + return false; + } + UUID targetId = getTargetPointer().getFirst(game, source); + for (UUID opponentId : game.getOpponents(source.getControllerId())) { + Optional.ofNullable(opponentId) + .filter(uuid -> !uuid.equals(targetId)) + .map(game::getPlayer) + .map(player -> player.damage(amount, source, game)); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/v/VislorTurlough.java b/Mage.Sets/src/mage/cards/v/VislorTurlough.java new file mode 100644 index 00000000000..169c3f520b1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VislorTurlough.java @@ -0,0 +1,129 @@ +package mage.cards.v; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.dynamicvalue.common.CardsInControllerHandCount; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.LoseLifeSourceControllerEffect; +import mage.abilities.effects.common.combat.GoadTargetEffect; +import mage.abilities.effects.common.continuous.GainControlTargetEffect; +import mage.abilities.keyword.DoctorsCompanionAbility; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPlayer; +import mage.target.common.TargetOpponent; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class VislorTurlough extends CardImpl { + + public VislorTurlough(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(2); + this.toughness = new MageInt(5); + + // Deal with the Black Guardian -- When Vislor Turlough enters the battlefield, you may have an opponent gain control of it. If you do, it's goaded for as long as they control it. + this.addAbility(new EntersBattlefieldTriggeredAbility(new VislorTurloughEffect(), true) + .withFlavorWord("Deal with the Black Guardian")); + + // At the beginning of your end step, draw a card, then you lose life equal to the number of cards in your hand. + Ability ability = new BeginningOfEndStepTriggeredAbility(new DrawCardSourceControllerEffect(1)); + ability.addEffect(new LoseLifeSourceControllerEffect(CardsInControllerHandCount.ANY) + .setText(", then you lose life equal to the number of cards in your hand")); + this.addAbility(ability); + + // Doctor's companion + this.addAbility(DoctorsCompanionAbility.getInstance()); + } + + private VislorTurlough(final VislorTurlough card) { + super(card); + } + + @Override + public VislorTurlough copy() { + return new VislorTurlough(this); + } +} + +class VislorTurloughEffect extends OneShotEffect { + + VislorTurloughEffect() { + super(Outcome.Benefit); + staticText = "have an opponent gain control of it. If you do, it's goaded for as long as they control it"; + } + + private VislorTurloughEffect(final VislorTurloughEffect effect) { + super(effect); + } + + @Override + public VislorTurloughEffect copy() { + return new VislorTurloughEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + if (player == null || permanent == null) { + return false; + } + TargetPlayer target = new TargetOpponent(0, 1, true); + player.choose(Outcome.GainControl, target, source, game); + Player opponent = game.getPlayer(target.getFirstTarget()); + if (opponent == null) { + return false; + } + game.addEffect(new GainControlTargetEffect( + Duration.Custom, true, opponent.getId() + ).setTargetPointer(new FixedTarget(permanent, game)), source); + game.addEffect(new VislorTurloughGoadEffect(opponent.getId(), permanent, game), source); + return true; + } +} + +class VislorTurloughGoadEffect extends GoadTargetEffect { + + private final UUID controllerId; + + VislorTurloughGoadEffect(UUID controllerId, Permanent permanent, Game game) { + super(Duration.Custom); + this.controllerId = controllerId; + this.setTargetPointer(new FixedTarget(permanent, game)); + } + + private VislorTurloughGoadEffect(final VislorTurloughGoadEffect effect) { + super(effect); + this.controllerId = effect.controllerId; + } + + @Override + public VislorTurloughGoadEffect copy() { + return new VislorTurloughGoadEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent != null && permanent.isControlledBy(controllerId)) { + return super.apply(game, source); + } + discard(); + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/v/ViviOrnitier.java b/Mage.Sets/src/mage/cards/v/ViviOrnitier.java new file mode 100644 index 00000000000..cb0b408ee68 --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/ViviOrnitier.java @@ -0,0 +1,85 @@ +package mage.cards.v; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.condition.common.MyTurnCondition; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.dynamicvalue.common.SourcePermanentPowerValue; +import mage.abilities.effects.common.DamagePlayersEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.effects.mana.AddManaInAnyCombinationEffect; +import mage.abilities.mana.ActivatedManaAbilityImpl; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class ViviOrnitier extends CardImpl { + + public ViviOrnitier(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(0); + this.toughness = new MageInt(3); + + // {0}: Add X mana in any combination of {U} and/or {R}, where X is Vivi Ornitier's power. Activate only during your turn and only once each turn. + this.addAbility(new ViviOrnitierManaAbility()); + + // Whenever you cast a noncreature spell, put a +1/+1 counter on Vivi Ornitier and it deals 1 damage to each opponent. + Ability ability = new SpellCastControllerTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), + StaticFilters.FILTER_SPELL_A_NON_CREATURE, false + ); + ability.addEffect(new DamagePlayersEffect(1, TargetController.OPPONENT, "it").concatBy("and")); + this.addAbility(ability); + } + + private ViviOrnitier(final ViviOrnitier card) { + super(card); + } + + @Override + public ViviOrnitier copy() { + return new ViviOrnitier(this); + } +} + +class ViviOrnitierManaAbility extends ActivatedManaAbilityImpl { + + ViviOrnitierManaAbility() { + super( + Zone.BATTLEFIELD, + new AddManaInAnyCombinationEffect( + SourcePermanentPowerValue.NOT_NEGATIVE, + SourcePermanentPowerValue.NOT_NEGATIVE, + ColoredManaSymbol.U, + ColoredManaSymbol.R + ), + new GenericManaCost(0) + ); + this.condition = MyTurnCondition.instance; + this.maxActivationsPerTurn = 1; + } + + private ViviOrnitierManaAbility(final ViviOrnitierManaAbility ability) { + super(ability); + } + + public ViviOrnitierManaAbility copy() { + return new ViviOrnitierManaAbility(this); + } + + @Override + public String getRule() { + return super.getRule() + " Activate only during your turn and only once each turn."; + } +} diff --git a/Mage.Sets/src/mage/cards/v/VizierOfTheMenagerie.java b/Mage.Sets/src/mage/cards/v/VizierOfTheMenagerie.java index ea4e9d7892a..7af5f276f73 100644 --- a/Mage.Sets/src/mage/cards/v/VizierOfTheMenagerie.java +++ b/Mage.Sets/src/mage/cards/v/VizierOfTheMenagerie.java @@ -57,7 +57,7 @@ class VizierOfTheMenagerieManaEffect extends AsThoughEffectImpl implements AsTho public VizierOfTheMenagerieManaEffect() { super(AsThoughEffectType.SPEND_OTHER_MANA, Duration.WhileOnBattlefield, Outcome.Benefit); - staticText = "You may spend mana as though it were mana of any type to cast creature spells"; + staticText = "You can spend mana of any type to cast creature spells"; } private VizierOfTheMenagerieManaEffect(final VizierOfTheMenagerieManaEffect effect) { diff --git a/Mage.Sets/src/mage/cards/v/VizkopaConfessor.java b/Mage.Sets/src/mage/cards/v/VizkopaConfessor.java index 698a105056d..a78fb030afc 100644 --- a/Mage.Sets/src/mage/cards/v/VizkopaConfessor.java +++ b/Mage.Sets/src/mage/cards/v/VizkopaConfessor.java @@ -79,7 +79,7 @@ class VizkopaConfessorEffect extends OneShotEffect { Player targetPlayer = game.getPlayer(source.getFirstTarget()); Card sourceCard = game.getCard(source.getSourceId()); if (controller != null && targetPlayer != null && sourceCard != null) { - int payLife = controller.getAmount(0, controller.getLife(),"Pay how many life?", game); + int payLife = controller.getAmount(0, controller.getLife(),"Pay how many life?", source, game); if (payLife > 0) { controller.loseLife(payLife, game, source, false); game.informPlayers(sourceCard.getName() + ": " + controller.getLogName() + " pays " + payLife + " life"); diff --git a/Mage.Sets/src/mage/cards/v/Void.java b/Mage.Sets/src/mage/cards/v/Void.java index 79062b8d7a4..68aa4acdee2 100644 --- a/Mage.Sets/src/mage/cards/v/Void.java +++ b/Mage.Sets/src/mage/cards/v/Void.java @@ -64,7 +64,7 @@ class VoidEffect extends OneShotEffect { return false; } - int number = controller.announceXMana(0, Integer.MAX_VALUE, this.staticText, game, source); + int number = controller.getAmount(0, Integer.MAX_VALUE, "Choose a number (mana cost to destroy)", source, game); game.informPlayers(controller.getLogName() + " chooses " + number + '.'); for (Permanent permanent : game.getBattlefield().getActivePermanents(source.getControllerId(), game)) { diff --git a/Mage.Sets/src/mage/cards/v/VolcanoHellion.java b/Mage.Sets/src/mage/cards/v/VolcanoHellion.java index c60f48ee61e..5928023abf8 100644 --- a/Mage.Sets/src/mage/cards/v/VolcanoHellion.java +++ b/Mage.Sets/src/mage/cards/v/VolcanoHellion.java @@ -82,7 +82,7 @@ class VolcanoHellionEffect extends OneShotEffect { } } else { //Human choose - amount = controller.getAmount(0, Integer.MAX_VALUE, "Choose the amount of damage to deliver to you and a target creature. The damage can't be prevented.", game); + amount = controller.getAmount(0, Integer.MAX_VALUE, "Choose the amount of damage to deliver to you and a target creature. The damage can't be prevented.", source, game); } if (amount > 0) { diff --git a/Mage.Sets/src/mage/cards/w/WallOfLimbs.java b/Mage.Sets/src/mage/cards/w/WallOfLimbs.java index b88bcd3540c..cb9a7472512 100644 --- a/Mage.Sets/src/mage/cards/w/WallOfLimbs.java +++ b/Mage.Sets/src/mage/cards/w/WallOfLimbs.java @@ -2,12 +2,11 @@ package mage.cards.w; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.GainLifeControllerTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.SacrificeSourceCost; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.dynamicvalue.common.SourcePermanentPowerValue; -import mage.abilities.effects.Effect; import mage.abilities.effects.common.LoseLifeTargetEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.keyword.DefenderAbility; @@ -15,22 +14,18 @@ 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.target.TargetPlayer; import java.util.UUID; /** - * * @author emerald000 */ public final class WallOfLimbs extends CardImpl { public WallOfLimbs(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{B}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); this.subtype.add(SubType.ZOMBIE); this.subtype.add(SubType.WALL); @@ -39,14 +34,14 @@ public final class WallOfLimbs extends CardImpl { // Defender this.addAbility(DefenderAbility.getInstance()); - + // Whenever you gain life, put a +1/+1 counter on Wall of Limbs. - this.addAbility(new WallOfLimbsTriggeredAbility()); - + this.addAbility(new GainLifeControllerTriggeredAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance()))); + // {5}{B}{B}, Sacrifice Wall of Limbs: Target player loses X life, where X is Wall of Limbs's power. - Effect effect = new LoseLifeTargetEffect(SourcePermanentPowerValue.NOT_NEGATIVE); - effect.setText("Target player loses X life, where X is {this}'s power."); - Ability ability = new SimpleActivatedAbility(effect, new ManaCostsImpl<>("{5}{B}{B}")); + Ability ability = new SimpleActivatedAbility( + new LoseLifeTargetEffect(SourcePermanentPowerValue.NOT_NEGATIVE), new ManaCostsImpl<>("{5}{B}{B}") + ); ability.addCost(new SacrificeSourceCost()); ability.addTarget(new TargetPlayer()); this.addAbility(ability); @@ -61,34 +56,3 @@ public final class WallOfLimbs extends CardImpl { return new WallOfLimbs(this); } } - -class WallOfLimbsTriggeredAbility extends TriggeredAbilityImpl { - - WallOfLimbsTriggeredAbility() { - super(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.P1P1.createInstance()), false); - } - - private WallOfLimbsTriggeredAbility(final WallOfLimbsTriggeredAbility ability) { - super(ability); - } - - @Override - public WallOfLimbsTriggeredAbility copy() { - return new WallOfLimbsTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.GAINED_LIFE; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - return event.getPlayerId().equals(controllerId); - } - - @Override - public String getRule() { - return "Whenever you gain life, put a +1/+1 counter on Wall of Limbs."; - } -} diff --git a/Mage.Sets/src/mage/cards/w/WarriorsSword.java b/Mage.Sets/src/mage/cards/w/WarriorsSword.java new file mode 100644 index 00000000000..02b90a950d2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WarriorsSword.java @@ -0,0 +1,49 @@ +package mage.cards.w; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.AddCardSubtypeAttachedEffect; +import mage.abilities.effects.common.continuous.BoostEquippedEffect; +import mage.abilities.keyword.EquipAbility; +import mage.abilities.keyword.JobSelectAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AttachmentType; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class WarriorsSword extends CardImpl { + + public WarriorsSword(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}{R}"); + + this.subtype.add(SubType.EQUIPMENT); + + // Job select + this.addAbility(new JobSelectAbility()); + + // Equipped creature gets +3/+2 and is a Warrior in addition to its other types. + Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(3, 2)); + ability.addEffect(new AddCardSubtypeAttachedEffect( + SubType.WARRIOR, AttachmentType.EQUIPMENT + ).setText(", and is a Warrior in addition to its other types")); + this.addAbility(ability); + + // Equip {5} + this.addAbility(new EquipAbility(5)); + } + + private WarriorsSword(final WarriorsSword card) { + super(card); + } + + @Override + public WarriorsSword copy() { + return new WarriorsSword(this); + } +} diff --git a/Mage.Sets/src/mage/cards/w/WeatheredSentinels.java b/Mage.Sets/src/mage/cards/w/WeatheredSentinels.java index f1c85b15d23..6b7b6ea3ec9 100644 --- a/Mage.Sets/src/mage/cards/w/WeatheredSentinels.java +++ b/Mage.Sets/src/mage/cards/w/WeatheredSentinels.java @@ -47,7 +47,7 @@ public class WeatheredSentinels extends CardImpl { new ConditionalAsThoughEffect( new CanAttackAsThoughItDidntHaveDefenderSourceEffect(Duration.WhileOnBattlefield), WeatheredSentinelsCanAttackSomeoneCondition.instance) - .setText("Weathered Sentinels can attack players who attacked you during their last turn as though it didn't have defender.")), + .setText("{this} can attack players who attacked you during their last turn as though it didn't have defender.")), new WeatheredSentinelsLastTurnAttackersWatcher() ); diff --git a/Mage.Sets/src/mage/cards/w/WeiAssassins.java b/Mage.Sets/src/mage/cards/w/WeiAssassins.java index d99f62b01a9..badf00beaf0 100644 --- a/Mage.Sets/src/mage/cards/w/WeiAssassins.java +++ b/Mage.Sets/src/mage/cards/w/WeiAssassins.java @@ -76,10 +76,7 @@ class WeiAssassinsEffect extends OneShotEffect { FilterCreaturePermanent filter = new FilterCreaturePermanent("creature you control"); filter.add(new ControllerIdPredicate(player.getId())); Target target = new TargetPermanent(1, 1, filter, true); - if (target.canChoose(player.getId(), source, game)) { - while (!target.isChosen(game) && target.canChoose(player.getId(), source, game) && player.canRespond()) { - player.chooseTarget(Outcome.DestroyPermanent, target, source, game); - } + if (player.chooseTarget(Outcome.DestroyPermanent, target, source, game)) { Permanent permanent = game.getPermanent(target.getFirstTarget()); if (permanent != null) { permanent.destroy(source, game, false); diff --git a/Mage.Sets/src/mage/cards/w/WelcomingVampire.java b/Mage.Sets/src/mage/cards/w/WelcomingVampire.java index 6d9e0038ae8..1b3ffd7a0c2 100644 --- a/Mage.Sets/src/mage/cards/w/WelcomingVampire.java +++ b/Mage.Sets/src/mage/cards/w/WelcomingVampire.java @@ -1,26 +1,27 @@ package mage.cards.w; -import java.util.UUID; import mage.MageInt; -import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; import mage.abilities.effects.common.DrawCardSourceControllerEffect; -import mage.constants.ComparisonType; -import mage.constants.SubType; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.common.FilterCreaturePermanent; +import mage.constants.ComparisonType; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.predicate.mageobject.AnotherPredicate; import mage.filter.predicate.mageobject.PowerPredicate; +import java.util.UUID; + /** - * * @author weirddan455 */ public final class WelcomingVampire extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("one or more other creatures with power 2 or less"); + private static final FilterPermanent filter = new FilterControlledCreaturePermanent("one or more other creatures with power 2 or less"); static { filter.add(AnotherPredicate.instance); @@ -38,7 +39,7 @@ public final class WelcomingVampire extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // Whenever one or more other creatures with power 2 or less enter the battlefield under your control, draw a card. This ability triggers only once each turn. - this.addAbility(new EntersBattlefieldControlledTriggeredAbility(new DrawCardSourceControllerEffect(1), filter).setTriggersLimitEachTurn(1)); + this.addAbility(new EntersBattlefieldAllTriggeredAbility(new DrawCardSourceControllerEffect(1), filter).setTriggersLimitEachTurn(1)); } private WelcomingVampire(final WelcomingVampire card) { diff --git a/Mage.Sets/src/mage/cards/w/WellOfLostDreams.java b/Mage.Sets/src/mage/cards/w/WellOfLostDreams.java index d21bdd918bd..cc808325600 100644 --- a/Mage.Sets/src/mage/cards/w/WellOfLostDreams.java +++ b/Mage.Sets/src/mage/cards/w/WellOfLostDreams.java @@ -59,7 +59,7 @@ class WellOfLostDreamsEffect extends OneShotEffect { if (controller != null) { int amount = SavedGainedLifeValue.MANY.calculate(game, source, this); if (amount > 0) { - int xValue = controller.announceXMana(0, amount, "Announce X Value", game, source); + int xValue = controller.announceX(0, amount, "Announce the value for {X} (pay for draw cards)", game, source, true); if (xValue > 0) { if (new GenericManaCost(xValue).pay(source, game, source, controller.getId(), false)) { game.informPlayers(controller.getLogName() + " payed {" + xValue + '}'); diff --git a/Mage.Sets/src/mage/cards/w/WheelOfMisfortune.java b/Mage.Sets/src/mage/cards/w/WheelOfMisfortune.java index 2633aae84db..f349c38ae35 100644 --- a/Mage.Sets/src/mage/cards/w/WheelOfMisfortune.java +++ b/Mage.Sets/src/mage/cards/w/WheelOfMisfortune.java @@ -63,7 +63,7 @@ class WheelOfMisfortuneEffect extends OneShotEffect { if (player == null) { continue; } - playerMap.put(playerId, player.getAmount(0, 1000, "Choose a number", game)); + playerMap.put(playerId, player.getAmount(0, 1000, "Choose a number", source, game)); } for (Map.Entry entry : playerMap.entrySet()) { Player player = game.getPlayer(entry.getKey()); diff --git a/Mage.Sets/src/mage/cards/w/WheelOfPotential.java b/Mage.Sets/src/mage/cards/w/WheelOfPotential.java index 5c32602230a..d4e776f3f76 100644 --- a/Mage.Sets/src/mage/cards/w/WheelOfPotential.java +++ b/Mage.Sets/src/mage/cards/w/WheelOfPotential.java @@ -67,7 +67,7 @@ class WheelOfPotentialEffect extends OneShotEffect { } int numberToPay = controller.getAmount( 0, controller.getCountersCount(CounterType.ENERGY), - "How many {E} do you want to pay?", game + "How many {E} do you want to pay?", source, game ); Cost cost = new PayEnergyCost(numberToPay); int numberPaid = 0; diff --git a/Mage.Sets/src/mage/cards/w/WhirlwingStormbrood.java b/Mage.Sets/src/mage/cards/w/WhirlwingStormbrood.java index aa95f5123da..126fbf83ee5 100644 --- a/Mage.Sets/src/mage/cards/w/WhirlwingStormbrood.java +++ b/Mage.Sets/src/mage/cards/w/WhirlwingStormbrood.java @@ -1,30 +1,25 @@ package mage.cards.w; -import java.util.UUID; import mage.MageInt; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.dynamicvalue.common.StaticValue; import mage.abilities.effects.common.continuous.CastAsThoughItHadFlashAllEffect; import mage.abilities.effects.common.counter.AddCountersTargetEffect; -import mage.cards.OmenCard; -import mage.constants.Duration; -import mage.constants.SubType; import mage.abilities.keyword.FlashAbility; import mage.abilities.keyword.FlyingAbility; -import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.cards.OmenCard; import mage.constants.CardType; -import mage.constants.Zone; +import mage.constants.Duration; +import mage.constants.SubType; import mage.counters.CounterType; import mage.filter.FilterCard; -import mage.filter.FilterSpell; -import mage.filter.StaticFilters; import mage.filter.predicate.Predicates; import mage.target.common.TargetControlledCreaturePermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; /** - * * @author Jmlundeen */ public final class WhirlwingStormbrood extends OmenCard { @@ -40,7 +35,7 @@ public final class WhirlwingStormbrood extends OmenCard { public WhirlwingStormbrood(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, new CardType[]{CardType.SORCERY}, "{4}{U}", "Dynamic Soar", "{2}{G}"); - + this.subtype.add(SubType.DRAGON); this.power = new MageInt(4); this.toughness = new MageInt(3); @@ -56,7 +51,7 @@ public final class WhirlwingStormbrood extends OmenCard { // Dynamic Soar // Put three +1/+1 counters on target creature you control. - this.getSpellCard().getSpellAbility().addEffect(new AddCountersTargetEffect(CounterType.P1P1.createInstance(), StaticValue.get(3))); + this.getSpellCard().getSpellAbility().addEffect(new AddCountersTargetEffect(CounterType.P1P1.createInstance(3), StaticValue.get(3))); this.getSpellCard().getSpellAbility().addTarget(new TargetControlledCreaturePermanent()); this.finalizeOmen(); } diff --git a/Mage.Sets/src/mage/cards/w/WhiteAuracite.java b/Mage.Sets/src/mage/cards/w/WhiteAuracite.java new file mode 100644 index 00000000000..6ef8e41b160 --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WhiteAuracite.java @@ -0,0 +1,40 @@ +package mage.cards.w; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.ExileUntilSourceLeavesEffect; +import mage.abilities.mana.WhiteManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class WhiteAuracite extends CardImpl { + + public WhiteAuracite(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{W}{W}"); + + // When this artifact enters, exile target nonland permanent an opponent controls until this artifact leaves the battlefield. + Ability ability = new EntersBattlefieldTriggeredAbility(new ExileUntilSourceLeavesEffect()); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_OPPONENTS_PERMANENT_NON_LAND)); + this.addAbility(ability); + + // {T}: Add {W}. + this.addAbility(new WhiteManaAbility()); + } + + private WhiteAuracite(final WhiteAuracite card) { + super(card); + } + + @Override + public WhiteAuracite copy() { + return new WhiteAuracite(this); + } +} diff --git a/Mage.Sets/src/mage/cards/w/WhiteMagesStaff.java b/Mage.Sets/src/mage/cards/w/WhiteMagesStaff.java new file mode 100644 index 00000000000..46c2540f4fd --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WhiteMagesStaff.java @@ -0,0 +1,57 @@ +package mage.cards.w; + +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.continuous.AddCardSubtypeAttachedEffect; +import mage.abilities.effects.common.continuous.BoostEquippedEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.keyword.EquipAbility; +import mage.abilities.keyword.JobSelectAbility; +import mage.constants.AttachmentType; +import mage.constants.SubType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; + +/** + * @author balazskristof + */ +public final class WhiteMagesStaff extends CardImpl { + + public WhiteMagesStaff(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{W}"); + + this.subtype.add(SubType.EQUIPMENT); + + // Job select + this.addAbility(new JobSelectAbility()); + + // Equipped creature gets +1/+1, has "Whenever this creature attacks, you gain 1 life," and is a Cleric in addition to its other types. + Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(1, 1)); + ability.addEffect(new GainAbilityAttachedEffect( + new AttacksTriggeredAbility(new GainLifeEffect(1)), + AttachmentType.EQUIPMENT + ).setText(", has \"Whenever this creature attacks, you gain 1 life,\"")); + ability.addEffect(new AddCardSubtypeAttachedEffect( + SubType.CLERIC, + AttachmentType.EQUIPMENT + ).setText("is a Cleric in addition to its other types").concatBy("and")); + this.addAbility(ability); + + // Equip {3} + this.addAbility(new EquipAbility(3)); + } + + private WhiteMagesStaff(final WhiteMagesStaff card) { + super(card); + } + + @Override + public WhiteMagesStaff copy() { + return new WhiteMagesStaff(this); + } +} diff --git a/Mage.Sets/src/mage/cards/w/WildbornPreserver.java b/Mage.Sets/src/mage/cards/w/WildbornPreserver.java index 64cb4c6b4b3..a0401674991 100644 --- a/Mage.Sets/src/mage/cards/w/WildbornPreserver.java +++ b/Mage.Sets/src/mage/cards/w/WildbornPreserver.java @@ -94,7 +94,7 @@ class WildbornPreserverCreateReflexiveTriggerEffect extends OneShotEffect { if (!player.chooseUse(outcome, "Pay " + cost.getText() + "?", source, game)) { return false; } - int costX = player.announceXMana(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source); + int costX = player.announceX(0, Integer.MAX_VALUE, "Announce the value for {X} (pay for counters)", game, source, true); cost.add(new GenericManaCost(costX)); if (!cost.pay(source, game, source, source.getControllerId(), false, null)) { return false; diff --git a/Mage.Sets/src/mage/cards/w/WilfredMott.java b/Mage.Sets/src/mage/cards/w/WilfredMott.java index ca81091b7d4..624c82b9d65 100644 --- a/Mage.Sets/src/mage/cards/w/WilfredMott.java +++ b/Mage.Sets/src/mage/cards/w/WilfredMott.java @@ -1,28 +1,24 @@ package mage.cards.w; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.CountersSourceCount; -import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.effects.common.LookLibraryAndPickControllerEffect; -import mage.constants.SubType; -import mage.constants.SuperType; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.ComparisonType; -import mage.constants.PutCards; +import mage.constants.*; import mage.counters.CounterType; import mage.filter.FilterCard; import mage.filter.common.FilterNonlandCard; import mage.filter.predicate.mageobject.ManaValuePredicate; import mage.filter.predicate.mageobject.PermanentPredicate; +import java.util.UUID; + /** - * * @author padfoot */ public final class WilfredMott extends CardImpl { @@ -32,12 +28,12 @@ public final class WilfredMott extends CardImpl { static { filter.add(PermanentPredicate.instance); - filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, 4)); + filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, 4)); } public WilfredMott(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); - + this.supertype.add(SuperType.LEGENDARY); this.subtype.add(SubType.HUMAN); this.subtype.add(SubType.SOLDIER); @@ -45,13 +41,15 @@ public final class WilfredMott extends CardImpl { this.toughness = new MageInt(4); // Look to the Stars -- At the beginning of your upkeep, put a time counter on Wilfred Mott. Then look at the top X cards of your library, where X is the number of time counters on Wilfred Mott. You may put a nonland permanent card with mana value 3 or less from among them onto the battlefield. Put the rest on the bottom of your library in a random order. - Ability ability = new BeginningOfUpkeepTriggeredAbility(new AddCountersSourceEffect(CounterType.TIME.createInstance())).withFlavorWord("Look to the Stars"); - LookLibraryAndPickControllerEffect effect = new LookLibraryAndPickControllerEffect(xValue, 1, filter, PutCards.BATTLEFIELD, PutCards.BOTTOM_RANDOM); - effect.setText("Then look at the top X cards of your library, where X is the number of time counters on Wilfred Mott. " + - " You may put a nonland permanent card with mana value 3 or less from among them onto the battlefield. " + - " Put the rest on the bottom of your library in a random order."); - ability.addEffect(effect); - this.addAbility(ability); + Ability ability = new BeginningOfUpkeepTriggeredAbility( + new AddCountersSourceEffect(CounterType.TIME.createInstance()) + ).withFlavorWord("Look to the Stars"); + ability.addEffect(new LookLibraryAndPickControllerEffect( + xValue, 1, filter, PutCards.BATTLEFIELD, PutCards.BOTTOM_RANDOM + ).setText("Then look at the top X cards of your library, where X is the number of time counters on {this}. " + + "You may put a nonland permanent card with mana value 3 or less from among them onto the battlefield. " + + "Put the rest on the bottom of your library in a random order.")); + this.addAbility(ability); } private WilfredMott(final WilfredMott card) { diff --git a/Mage.Sets/src/mage/cards/w/WillOfTheSultai.java b/Mage.Sets/src/mage/cards/w/WillOfTheSultai.java index fa49ce6513b..f86fd8d497f 100644 --- a/Mage.Sets/src/mage/cards/w/WillOfTheSultai.java +++ b/Mage.Sets/src/mage/cards/w/WillOfTheSultai.java @@ -36,7 +36,7 @@ public final class WillOfTheSultai extends CardImpl { // * Target player mills three cards. Return all land cards from your graveyard to the battlefield tapped. this.getSpellAbility().addEffect(new MillCardsTargetEffect(3)); this.getSpellAbility().addTarget(new TargetPlayer()); - this.getSpellAbility().addEffect(new ReturnFromYourGraveyardToBattlefieldAllEffect(StaticFilters.FILTER_CARD_LANDS)); + this.getSpellAbility().addEffect(new ReturnFromYourGraveyardToBattlefieldAllEffect(StaticFilters.FILTER_CARD_LANDS, true)); // * Put X +1/+1 counters on target creature, where X is the number of lands you control. It gains trample until end of turn. this.getSpellAbility().addMode(new Mode(new AddCountersTargetEffect( diff --git a/Mage.Sets/src/mage/cards/w/WithinRange.java b/Mage.Sets/src/mage/cards/w/WithinRange.java index e75296feaa7..83fc0a9c88b 100644 --- a/Mage.Sets/src/mage/cards/w/WithinRange.java +++ b/Mage.Sets/src/mage/cards/w/WithinRange.java @@ -26,7 +26,7 @@ public final class WithinRange extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{B}"); // When this enchantment enters, create two 1/1 red Warrior creature tokens. - this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new RedWarriorToken()))); + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new RedWarriorToken(), 2))); // Whenever you attack, each opponent loses life equal to the number of creatures attacking them. this.addAbility(new AttacksWithCreaturesTriggeredAbility(new WithinRangeEffect(), 1)); diff --git a/Mage.Sets/src/mage/cards/w/Wonder.java b/Mage.Sets/src/mage/cards/w/Wonder.java index 3b47e8b642c..2b0e19eb5ca 100644 --- a/Mage.Sets/src/mage/cards/w/Wonder.java +++ b/Mage.Sets/src/mage/cards/w/Wonder.java @@ -1,12 +1,10 @@ - package mage.cards.w; -import java.util.UUID; import mage.MageInt; import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; import mage.abilities.decorator.ConditionalContinuousEffect; -import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; @@ -15,26 +13,20 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; import mage.constants.Zone; +import mage.filter.StaticFilters; import mage.filter.common.FilterControlledPermanent; -import mage.filter.common.FilterCreaturePermanent; + +import java.util.UUID; /** - * * @author magenoxx_at_gmail.com */ public final class Wonder extends CardImpl { - private static final String ruleText = "As long as Wonder is in your graveyard and you control an Island, creatures you control have flying"; - - private static final FilterControlledPermanent filter = new FilterControlledPermanent("Island"); - - static { - filter.add(CardType.LAND.getPredicate()); - filter.add(SubType.ISLAND.getPredicate()); - } + private static final Condition condition = new PermanentsOnTheBattlefieldCondition(new FilterControlledPermanent(SubType.ISLAND)); public Wonder(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.INCARNATION); this.power = new MageInt(2); @@ -44,11 +36,12 @@ public final class Wonder extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // As long as Wonder is in your graveyard and you control an Island, creatures you control have flying. - ContinuousEffect effect = new GainAbilityControlledEffect(FlyingAbility.getInstance(), - Duration.WhileOnBattlefield, new FilterCreaturePermanent()); - ConditionalContinuousEffect wonderEffect = new ConditionalContinuousEffect(effect, - new PermanentsOnTheBattlefieldCondition(filter), ruleText); - this.addAbility(new SimpleStaticAbility(Zone.GRAVEYARD, wonderEffect)); + this.addAbility(new SimpleStaticAbility( + Zone.GRAVEYARD, + new ConditionalContinuousEffect(new GainAbilityControlledEffect( + FlyingAbility.getInstance(), Duration.WhileOnBattlefield, StaticFilters.FILTER_PERMANENT_CREATURE + ), condition, "as long as this card is in your graveyard and you control an Island, creatures you control have flying") + )); } private Wonder(final Wonder card) { diff --git a/Mage.Sets/src/mage/cards/w/WondrousCrucible.java b/Mage.Sets/src/mage/cards/w/WondrousCrucible.java index 5e27f5f7396..51d0a6295d1 100644 --- a/Mage.Sets/src/mage/cards/w/WondrousCrucible.java +++ b/Mage.Sets/src/mage/cards/w/WondrousCrucible.java @@ -2,16 +2,19 @@ package mage.cards.w; import mage.ApprovingObject; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; import mage.abilities.keyword.WardAbility; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +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; @@ -29,13 +32,12 @@ public final class WondrousCrucible extends CardImpl { // Permanents you control have ward {2}. this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect( - new WardAbility(new GenericManaCost(2), false), Duration.WhileOnBattlefield + new WardAbility(new GenericManaCost(2), false), + Duration.WhileOnBattlefield, StaticFilters.FILTER_PERMANENTS ))); // At the beginning of your end step, mill two cards, then exile a nonland card at random from your graveyard. Copy it. You may cast the copy without paying its mana cost. - this.addAbility(new BeginningOfEndStepTriggeredAbility( - new WondrousCrucibleEffect() - )); + this.addAbility(new BeginningOfEndStepTriggeredAbility(new WondrousCrucibleEffect())); } private WondrousCrucible(final WondrousCrucible card) { diff --git a/Mage.Sets/src/mage/cards/w/WrathOfTheSkies.java b/Mage.Sets/src/mage/cards/w/WrathOfTheSkies.java index c8fee5f8ad8..b1418341509 100644 --- a/Mage.Sets/src/mage/cards/w/WrathOfTheSkies.java +++ b/Mage.Sets/src/mage/cards/w/WrathOfTheSkies.java @@ -68,7 +68,7 @@ class WrathOfTheSkiesEffect extends OneShotEffect { } int numberToPay = controller.getAmount(0, controller.getCountersCount(CounterType.ENERGY), - "Pay any amount of {E}", game); + "Pay any amount of {E}", source, game); Cost cost = new PayEnergyCost(numberToPay); if (cost.pay(source, game, source, source.getControllerId(), true)) { game.getBattlefield() diff --git a/Mage.Sets/src/mage/cards/w/WreckingBallArm.java b/Mage.Sets/src/mage/cards/w/WreckingBallArm.java new file mode 100644 index 00000000000..070a0f47d48 --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WreckingBallArm.java @@ -0,0 +1,56 @@ +package mage.cards.w; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.combat.CantBeBlockedByCreaturesAttachedEffect; +import mage.abilities.effects.common.continuous.SetBasePowerToughnessAttachedEffect; +import mage.abilities.keyword.DauntAbility; +import mage.abilities.keyword.EquipAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class WreckingBallArm extends CardImpl { + + public WreckingBallArm(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.EQUIPMENT); + + // Equipped creature has base power and toughness 7/7 and can't be blocked by creatures with power 2 or less. + Ability ability = new SimpleStaticAbility(new SetBasePowerToughnessAttachedEffect( + 7, 7, AttachmentType.EQUIPMENT + )); + ability.addEffect(new CantBeBlockedByCreaturesAttachedEffect( + Duration.WhileControlled, DauntAbility.getFilter(), AttachmentType.EQUIPMENT + )); + this.addAbility(ability); + + // Equip legendary creature {3} + this.addAbility(new EquipAbility( + Outcome.BoostCreature, new GenericManaCost(3), + new TargetPermanent(StaticFilters.FILTER_CONTROLLED_CREATURE_LEGENDARY) + )); + + // Equip {7} + this.addAbility(new EquipAbility(7)); + } + + private WreckingBallArm(final WreckingBallArm card) { + super(card); + } + + @Override + public WreckingBallArm copy() { + return new WreckingBallArm(this); + } +} diff --git a/Mage.Sets/src/mage/cards/x/XandeDarkMage.java b/Mage.Sets/src/mage/cards/x/XandeDarkMage.java new file mode 100644 index 00000000000..8c287e2c752 --- /dev/null +++ b/Mage.Sets/src/mage/cards/x/XandeDarkMage.java @@ -0,0 +1,58 @@ +package mage.cards.x; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicates; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class XandeDarkMage extends CardImpl { + + private static final FilterCard filter = new FilterCard("noncreature, nonland card in your graveyard"); + + static { + filter.add(Predicates.not(CardType.CREATURE.getPredicate())); + filter.add(Predicates.not(CardType.LAND.getPredicate())); + } + + private static final DynamicValue xValue = new CardsInControllerGraveyardCount(filter); + + public XandeDarkMage(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Menace + this.addAbility(new MenaceAbility()); + + // Xande gets +1/+1 for each noncreature, nonland card in your graveyard. + this.addAbility(new SimpleStaticAbility(new BoostSourceEffect(xValue, xValue, Duration.WhileOnBattlefield))); + } + + private XandeDarkMage(final XandeDarkMage card) { + super(card); + } + + @Override + public XandeDarkMage copy() { + return new XandeDarkMage(this); + } +} diff --git a/Mage.Sets/src/mage/cards/x/XenagosTheReveler.java b/Mage.Sets/src/mage/cards/x/XenagosTheReveler.java index b4cbddf703b..dbdc230445c 100644 --- a/Mage.Sets/src/mage/cards/x/XenagosTheReveler.java +++ b/Mage.Sets/src/mage/cards/x/XenagosTheReveler.java @@ -81,7 +81,7 @@ class XenagosManaEffect extends OneShotEffect { } Mana mana = new Mana(); - int redCount = player.getAmount(0, x, "How much RED mana add to pool? (available: " + x + ", another mana goes to GREEN)?", game); + int redCount = player.getAmount(0, x, "How much RED mana add to pool? (available: " + x + ", another mana goes to GREEN)?", source, game); int greenCount = Math.max(0, x - redCount); mana.setRed(redCount); mana.setGreen(greenCount); diff --git a/Mage.Sets/src/mage/cards/y/YgraEaterOfAll.java b/Mage.Sets/src/mage/cards/y/YgraEaterOfAll.java index 99e53ef30ea..30f3321fe66 100644 --- a/Mage.Sets/src/mage/cards/y/YgraEaterOfAll.java +++ b/Mage.Sets/src/mage/cards/y/YgraEaterOfAll.java @@ -88,7 +88,7 @@ class YgraEaterOfAllEffect extends ContinuousEffectImpl { permanent.addSubType(game, SubType.FOOD); break; case AbilityAddingRemovingEffects_6: - permanent.addAbility(new FoodAbility(true), source.getSourceId(), game); + permanent.addAbility(new FoodAbility(), source.getSourceId(), game); break; } } diff --git a/Mage.Sets/src/mage/cards/y/YoureNotAlone.java b/Mage.Sets/src/mage/cards/y/YoureNotAlone.java new file mode 100644 index 00000000000..8a2fdc80b80 --- /dev/null +++ b/Mage.Sets/src/mage/cards/y/YoureNotAlone.java @@ -0,0 +1,49 @@ +package mage.cards.y; + +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.AddContinuousEffectToGame; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.hint.common.CreaturesYouControlHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.filter.StaticFilters; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class YoureNotAlone extends CardImpl { + + private static final Condition condition = new PermanentsOnTheBattlefieldCondition( + StaticFilters.FILTER_CONTROLLED_CREATURE, ComparisonType.MORE_THAN, 2 + ); + + public YoureNotAlone(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{W}"); + + // Target creature gets +2/+2 until end of turn. If you control three or more creatures, it gets +4/+4 until end of turn instead. + this.getSpellAbility().addEffect(new ConditionalOneShotEffect( + new AddContinuousEffectToGame(new BoostTargetEffect(4, 4)), + new AddContinuousEffectToGame(new BoostTargetEffect(2, 2)), + condition, "target creature gets +2/+2 until end of turn. " + + "If you control three or more creatures, it gets +4/+4 until end of turn instead" + )); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + this.getSpellAbility().addHint(CreaturesYouControlHint.instance); + } + + private YoureNotAlone(final YoureNotAlone card) { + super(card); + } + + @Override + public YoureNotAlone copy() { + return new YoureNotAlone(this); + } +} diff --git a/Mage.Sets/src/mage/cards/y/YshtolaRhul.java b/Mage.Sets/src/mage/cards/y/YshtolaRhul.java new file mode 100644 index 00000000000..2083b4ed242 --- /dev/null +++ b/Mage.Sets/src/mage/cards/y/YshtolaRhul.java @@ -0,0 +1,74 @@ +package mage.cards.y; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileThenReturnTargetEffect; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.TurnPhase; +import mage.game.Game; +import mage.game.turn.TurnMod; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; + +/** + * @author balazskristof + */ +public final class YshtolaRhul extends CardImpl { + + public YshtolaRhul(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{U}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.CAT); + this.subtype.add(SubType.DRUID); + this.power = new MageInt(3); + this.toughness = new MageInt(5); + + // At the beginning of your end step, exile target creature you control, then return it to the battlefield under its owner's control. Then if it's the first end step of the turn, there is an additional end step after this step. + Ability ability = new BeginningOfEndStepTriggeredAbility(new ExileThenReturnTargetEffect(true, false)); + ability.addTarget(new TargetControlledCreaturePermanent()); + ability.addEffect(new YshtolaRhulEffect().concatBy("Then")); + this.addAbility(ability); + } + + private YshtolaRhul(final YshtolaRhul card) { + super(card); + } + + @Override + public YshtolaRhul copy() { + return new YshtolaRhul(this); + } +} + +class YshtolaRhulEffect extends OneShotEffect { + public YshtolaRhulEffect() { + super(Outcome.Benefit); + staticText = "if it's the first end step of the turn, there is an additional end step after this step"; + } + + protected YshtolaRhulEffect(final YshtolaRhulEffect effect) { + super(effect); + } + + @Override + public YshtolaRhulEffect copy() { + return new YshtolaRhulEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + if (game.getTurn().getPhase(TurnPhase.END).getCount() == 0) { + TurnMod end = new TurnMod(game.getState().getActivePlayerId()).withExtraPhase(TurnPhase.END); + game.getState().getTurnMods().add(end); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/y/YuffieMateriaHunter.java b/Mage.Sets/src/mage/cards/y/YuffieMateriaHunter.java new file mode 100644 index 00000000000..fa451bcef34 --- /dev/null +++ b/Mage.Sets/src/mage/cards/y/YuffieMateriaHunter.java @@ -0,0 +1,85 @@ +package mage.cards.y; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.GainControlTargetEffect; +import mage.abilities.keyword.NinjutsuAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledPermanent; +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 YuffieMateriaHunter extends CardImpl { + + public YuffieMateriaHunter(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.NINJA); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Ninjutsu {1}{R} + this.addAbility(new NinjutsuAbility("{1}{R}")); + + // When Yuffie enters, gain control of target noncreature artifact for as long as you control Yuffie. Then you may attach an Equipment you control to Yuffie. + Ability ability = new EntersBattlefieldTriggeredAbility(new GainControlTargetEffect(Duration.WhileControlled)); + ability.addEffect(new YuffieMateriaHunterEffect()); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_ARTIFACT_NON_CREATURE)); + this.addAbility(ability); + } + + private YuffieMateriaHunter(final YuffieMateriaHunter card) { + super(card); + } + + @Override + public YuffieMateriaHunter copy() { + return new YuffieMateriaHunter(this); + } +} + +class YuffieMateriaHunterEffect extends OneShotEffect { + + private static final FilterPermanent filter = new FilterControlledPermanent(SubType.EQUIPMENT); + + YuffieMateriaHunterEffect() { + super(Outcome.Benefit); + staticText = "Then you may attach an Equipment you control to {this}"; + } + + private YuffieMateriaHunterEffect(final YuffieMateriaHunterEffect effect) { + super(effect); + } + + @Override + public YuffieMateriaHunterEffect copy() { + return new YuffieMateriaHunterEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + if (player == null || permanent == null) { + return false; + } + TargetPermanent target = new TargetPermanent(0, 1, filter, true); + player.choose(outcome, target, source, game); + return permanent.addAttachment(target.getFirstTarget(), source, game); + } +} diff --git a/Mage.Sets/src/mage/cards/y/YunaHopeOfSpira.java b/Mage.Sets/src/mage/cards/y/YunaHopeOfSpira.java new file mode 100644 index 00000000000..fcc138cc772 --- /dev/null +++ b/Mage.Sets/src/mage/cards/y/YunaHopeOfSpira.java @@ -0,0 +1,84 @@ +package mage.cards.y; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.MyTurnCondition; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldWithCounterTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityAllEffect; +import mage.abilities.keyword.LifelinkAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.abilities.keyword.WardAbility; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +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.FilterCard; +import mage.filter.FilterPermanent; +import mage.filter.FilterPermanentThisOrAnother; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterEnchantmentCard; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class YunaHopeOfSpira extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledCreaturePermanent(); + + static { + filter.add(CardType.ENCHANTMENT.getPredicate()); + } + + private static final FilterPermanent filter2 = new FilterPermanentThisOrAnother(filter, false); + + private static final FilterCard filter3 = new FilterEnchantmentCard("enchantment card from your graveyard"); + + public YunaHopeOfSpira(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.CLERIC); + this.power = new MageInt(3); + this.toughness = new MageInt(5); + + // During your turn, Yuna and enchantment creatures you control have trample, lifelink, and ward {2}. + Ability ability = new SimpleStaticAbility(new ConditionalContinuousEffect( + new GainAbilityAllEffect(TrampleAbility.getInstance(), Duration.WhileControlled, filter2), + MyTurnCondition.instance, "during your turn, {this} and enchantment creatures you control have trample" + )); + ability.addEffect(new ConditionalContinuousEffect(new GainAbilityAllEffect( + LifelinkAbility.getInstance(), Duration.WhileControlled, filter2 + ), MyTurnCondition.instance, ", lifelink")); + ability.addEffect(new ConditionalContinuousEffect(new GainAbilityAllEffect( + new WardAbility(new GenericManaCost(2)), Duration.WhileControlled, filter2 + ), MyTurnCondition.instance, ", and ward {2}")); + this.addAbility(ability); + + // At the beginning of your end step, return up to one target enchantment card from your graveyard to the battlefield with a finality counter on it. + ability = new BeginningOfEndStepTriggeredAbility( + new ReturnFromGraveyardToBattlefieldWithCounterTargetEffect(CounterType.FINALITY.createInstance()) + ); + ability.addTarget(new TargetCardInYourGraveyard(0, 1, filter3)); + this.addAbility(ability); + } + + private YunaHopeOfSpira(final YunaHopeOfSpira card) { + super(card); + } + + @Override + public YunaHopeOfSpira copy() { + return new YunaHopeOfSpira(this); + } +} diff --git a/Mage.Sets/src/mage/cards/y/YunasDecision.java b/Mage.Sets/src/mage/cards/y/YunasDecision.java new file mode 100644 index 00000000000..a65e84c1b05 --- /dev/null +++ b/Mage.Sets/src/mage/cards/y/YunasDecision.java @@ -0,0 +1,133 @@ +package mage.cards.y; + +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.assignment.common.CardTypeAssignment; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect; +import mage.cards.*; +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.TargetCardInHand; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class YunasDecision extends CardImpl { + + public YunasDecision(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{G}"); + + // Choose one -- + // * Continue the Pilgrimage -- Sacrifice a creature. If you do, draw a card, then you may put a creature card and/or a land card from your hand onto the battlefield. + this.getSpellAbility().addEffect(new DoIfCostPaid( + new DrawCardSourceControllerEffect(1), + new SacrificeTargetCost(StaticFilters.FILTER_PERMANENT_CREATURE), null, false + ).addEffect(new YunasDecisionEffect())); + this.getSpellAbility().withFirstModeFlavorWord("Continue the Pilgrimage"); + + // * Find Another Way -- Return one or two target permanent cards from your graveyard to your hand. + this.getSpellAbility().addMode( + new Mode(new ReturnFromGraveyardToHandTargetEffect()) + .addTarget(new TargetCardInYourGraveyard( + 1, 2, StaticFilters.FILTER_CARD_PERMANENTS + )) + .withFlavorWord("Find Another Way") + ); + } + + private YunasDecision(final YunasDecision card) { + super(card); + } + + @Override + public YunasDecision copy() { + return new YunasDecision(this); + } +} + +class YunasDecisionEffect extends OneShotEffect { + + YunasDecisionEffect() { + super(Outcome.Benefit); + staticText = ", then you may put a creature card and/or a land card from your hand onto the battlefield"; + } + + private YunasDecisionEffect(final YunasDecisionEffect effect) { + super(effect); + } + + @Override + public YunasDecisionEffect copy() { + return new YunasDecisionEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + TargetCard target = new YunasDecisionTarget(); + player.choose(outcome, player.getHand(), target, source, game); + return player.moveCards( + new CardsImpl(target.getTargets()).getCards(game), Zone.BATTLEFIELD, source, + game, true, false, false, null + ); + } +} + +class YunasDecisionTarget extends TargetCardInHand { + + private static final CardTypeAssignment cardTypeAssigner = new CardTypeAssignment(CardType.CREATURE, CardType.LAND); + + YunasDecisionTarget() { + super(0, 2, StaticFilters.FILTER_CARD_CREATURE_OR_LAND); + } + + private YunasDecisionTarget(final YunasDecisionTarget target) { + super(target); + } + + @Override + public YunasDecisionTarget copy() { + return new YunasDecisionTarget(this); + } + + @Override + public boolean canTarget(UUID playerId, UUID id, Ability ability, Game game) { + if (!super.canTarget(playerId, id, ability, game)) { + return false; + } + Card card = game.getCard(id); + if (card == null) { + return false; + } + if (this.getTargets().isEmpty()) { + return true; + } + Cards cards = new CardsImpl(this.getTargets()); + cards.add(card); + return cardTypeAssigner.getRoleCount(cards, game) >= cards.size(); + } + + + @Override + public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { + Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); + possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, null, game)); + return possibleTargets; + } +} diff --git a/Mage.Sets/src/mage/cards/y/YunasWhistle.java b/Mage.Sets/src/mage/cards/y/YunasWhistle.java new file mode 100644 index 00000000000..67ca2404011 --- /dev/null +++ b/Mage.Sets/src/mage/cards/y/YunasWhistle.java @@ -0,0 +1,91 @@ +package mage.cards.y; + +import mage.abilities.Ability; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.cards.*; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class YunasWhistle extends CardImpl { + + public YunasWhistle(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{G}{G}"); + + // Reveal cards from the top of your library until you reveal a creature card. Put that card into your hand and the rest on the bottom of your library in a random order. When you reveal a creature card this way, put X+1/+1 counters on target creature you control, where X is the mana value of that card. + this.getSpellAbility().addEffect(new YunasWhistleEffect()); + } + + private YunasWhistle(final YunasWhistle card) { + super(card); + } + + @Override + public YunasWhistle copy() { + return new YunasWhistle(this); + } +} + +class YunasWhistleEffect extends OneShotEffect { + + YunasWhistleEffect() { + super(Outcome.Benefit); + staticText = "reveal cards from the top of your library until you reveal a creature card. " + + "Put that card into your hand and the rest on the bottom of your library in a random order. " + + "When you reveal a creature card this way, put X +1/+1 counters on target creature you control, " + + "where X is the mana value of that card"; + } + + private YunasWhistleEffect(final YunasWhistleEffect effect) { + super(effect); + } + + @Override + public YunasWhistleEffect copy() { + return new YunasWhistleEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Cards cards = new CardsImpl(); + Card card = getCard(player, cards, game, source); + player.revealCards(source, cards, game); + if (card == null) { + return player.putCardsOnBottomOfLibrary(cards, game, source, false); + } + player.moveCards(card, Zone.HAND, source, game); + ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility( + new AddCountersTargetEffect(CounterType.P1P1.createInstance(card.getManaValue())), false + ); + ability.addTarget(new TargetControlledCreaturePermanent()); + game.fireReflexiveTriggeredAbility(ability, source); + cards.retainZone(Zone.LIBRARY, game); + player.putCardsOnBottomOfLibrary(cards, game, source, false); + return true; + } + + private static Card getCard(Player player, Cards cards, Game game, Ability source) { + for (Card card : player.getLibrary().getCards(game)) { + cards.add(card); + if (card.isCreature(game)) { + return card; + } + } + return null; + } +} diff --git a/Mage.Sets/src/mage/cards/y/YusriFortunesFlame.java b/Mage.Sets/src/mage/cards/y/YusriFortunesFlame.java index 84fe2942424..5b85ce922c9 100644 --- a/Mage.Sets/src/mage/cards/y/YusriFortunesFlame.java +++ b/Mage.Sets/src/mage/cards/y/YusriFortunesFlame.java @@ -69,7 +69,7 @@ class YusriFortunesFlameEffect extends OneShotEffect { if (player == null) { return false; } - int flips = player.getAmount(1, 5, "Choose a number between 1 and 5", game); + int flips = player.getAmount(1, 5, "Choose a number between 1 and 5", source, game); int wins = 0; int losses = 0; for (int i = 0; i < flips; i++) { diff --git a/Mage.Sets/src/mage/cards/z/ZanarkandAncientMetropolis.java b/Mage.Sets/src/mage/cards/z/ZanarkandAncientMetropolis.java new file mode 100644 index 00000000000..7a5e1fd4028 --- /dev/null +++ b/Mage.Sets/src/mage/cards/z/ZanarkandAncientMetropolis.java @@ -0,0 +1,87 @@ +package mage.cards.z; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTappedAbility; +import mage.abilities.dynamicvalue.common.LandsYouControlCount; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.hint.common.LandsYouControlHint; +import mage.abilities.mana.GreenManaAbility; +import mage.cards.AdventureCard; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.HeroToken; +import mage.game.permanent.token.Token; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ZanarkandAncientMetropolis extends AdventureCard { + + public ZanarkandAncientMetropolis(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, new CardType[]{CardType.SORCERY}, "", "Lasting Fayth", "{4}{G}{G}"); + + this.subtype.add(SubType.TOWN); + + // This land enters tapped. + this.addAbility(new EntersBattlefieldTappedAbility()); + + // {T}: Add {G}. + this.addAbility(new GreenManaAbility()); + + // Lasting Fayth + // Create a 1/1 colorless Hero creature token. Put a +1/+1 counter on it for each land you control. + this.getSpellCard().getSpellAbility().addEffect(new ZanarkandAncientMetropolisEffect()); + this.getSpellCard().getSpellAbility().addHint(LandsYouControlHint.instance); + this.finalizeAdventure(); + } + + private ZanarkandAncientMetropolis(final ZanarkandAncientMetropolis card) { + super(card); + } + + @Override + public ZanarkandAncientMetropolis copy() { + return new ZanarkandAncientMetropolis(this); + } +} + +class ZanarkandAncientMetropolisEffect extends OneShotEffect { + + ZanarkandAncientMetropolisEffect() { + super(Outcome.Benefit); + staticText = "create a 1/1 colorless Hero creature token. Put a +1/+1 counter on it for each land you control"; + } + + private ZanarkandAncientMetropolisEffect(final ZanarkandAncientMetropolisEffect effect) { + super(effect); + } + + @Override + public ZanarkandAncientMetropolisEffect copy() { + return new ZanarkandAncientMetropolisEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Token token = new HeroToken(); + token.putOntoBattlefield(1, game, source); + int amount = LandsYouControlCount.instance.calculate(game, source, this); + if (amount < 1) { + return true; + } + for (UUID tokenId : token.getLastAddedTokenIds()) { + Permanent permanent = game.getPermanent(tokenId); + if (permanent != null) { + permanent.addCounters(CounterType.P1P1.createInstance(amount), source, game); + } + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/z/ZedruuTheGreathearted.java b/Mage.Sets/src/mage/cards/z/ZedruuTheGreathearted.java index a61dbd79441..e04c58dd0d5 100644 --- a/Mage.Sets/src/mage/cards/z/ZedruuTheGreathearted.java +++ b/Mage.Sets/src/mage/cards/z/ZedruuTheGreathearted.java @@ -2,6 +2,7 @@ package mage.cards.z; import mage.MageInt; import mage.abilities.Ability; +import mage.abilities.hint.ValueHint; import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.ManaCostsImpl; @@ -38,7 +39,7 @@ public final class ZedruuTheGreathearted extends CardImpl { effect = new DrawCardSourceControllerEffect(PermanentsYouOwnThatOpponentsControlCount.instance); effect.setText("and draw X cards, where X is the number of permanents you own that your opponents control"); ability.addEffect(effect); - this.addAbility(ability); + this.addAbility(ability.addHint(new ValueHint("Permanents you own that your opponents control", PermanentsYouOwnThatOpponentsControlCount.instance))); // {R}{W}{U}: Target opponent gains control of target permanent you control. ability = new SimpleActivatedAbility(new TargetPlayerGainControlTargetPermanentEffect(), new ManaCostsImpl<>("{U}{R}{W}")); diff --git a/Mage.Sets/src/mage/cards/z/ZenosYaeGalvus.java b/Mage.Sets/src/mage/cards/z/ZenosYaeGalvus.java new file mode 100644 index 00000000000..5db0ae321ec --- /dev/null +++ b/Mage.Sets/src/mage/cards/z/ZenosYaeGalvus.java @@ -0,0 +1,102 @@ +package mage.cards.z; + +import mage.MageInt; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.LeavesBattlefieldAllTriggeredAbility; +import mage.abilities.effects.common.ChooseCreatureEffect; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.effects.common.continuous.BoostAllEffect; +import mage.abilities.keyword.TransformAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.util.CardUtil; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ZenosYaeGalvus extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(); + private static final FilterPermanent filter2 = new FilterPermanent(); + + static { + filter.add(ZenosYaeGalvusPredicate.FALSE); + filter2.add(ZenosYaeGalvusPredicate.TRUE); + } + + public ZenosYaeGalvus(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.NOBLE); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + this.secondSideCardClazz = mage.cards.s.ShinryuTranscendentRival.class; + + // My First Friend -- When Zenos yae Galvus enters, choose a creature an opponent controls. Until end of turn, creatures other than Zenos yae Galvus and the chosen creature get -2/-2. + Ability ability = new EntersBattlefieldTriggeredAbility( + new ChooseCreatureEffect(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE, false) + ); + ability.addEffect(new BoostAllEffect( + -2, -2, Duration.EndOfTurn, filter, true + ).setText("until end of turn, creatures other than {this} and the chosen creature get -2/-2")); + this.addAbility(ability.withFlavorWord("My First Friend")); + + // When the chosen creature leaves the battlefield, transform Zenos yae Galvus. + this.addAbility(new TransformAbility()); + this.addAbility(new LeavesBattlefieldAllTriggeredAbility( + new TransformSourceEffect(), filter2 + ).setTriggerPhrase("When the chosen creature leaves the battlefield, ")); + } + + private ZenosYaeGalvus(final ZenosYaeGalvus card) { + super(card); + } + + @Override + public ZenosYaeGalvus copy() { + return new ZenosYaeGalvus(this); + } +} + +enum ZenosYaeGalvusPredicate implements ObjectSourcePlayerPredicate { + TRUE(true), + FALSE(false); + private final boolean flag; + + ZenosYaeGalvusPredicate(boolean flag) { + this.flag = flag; + } + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + return flag == Optional + .ofNullable(CardUtil.getObjectZoneString( + "chosenCreature", input.getSource().getSourceId(), game, + input.getSource().getSourceObjectZoneChangeCounter(), false + )) + .map(game.getState()::getValue) + .filter(MageObjectReference.class::isInstance) + .map(MageObjectReference.class::cast) + .map(mor -> mor.refersTo(input.getObject(), game)) + .orElse(false); + } +} diff --git a/Mage.Sets/src/mage/cards/z/ZurgoThundersDecree.java b/Mage.Sets/src/mage/cards/z/ZurgoThundersDecree.java index be9b2e959ff..27ce789f62c 100644 --- a/Mage.Sets/src/mage/cards/z/ZurgoThundersDecree.java +++ b/Mage.Sets/src/mage/cards/z/ZurgoThundersDecree.java @@ -43,7 +43,8 @@ public final class ZurgoThundersDecree extends CardImpl { // During your end step, Warrior tokens you control have "This token can't be sacrificed." this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect(new GainAbilityControlledEffect( - new SimpleStaticAbility(new CantBeSacrificedSourceEffect()), Duration.WhileOnBattlefield + new SimpleStaticAbility(new CantBeSacrificedSourceEffect()), + Duration.WhileOnBattlefield, filter ), condition, "during your end step, Warrior tokens you control have \"This token can't be sacrificed.\""))); } diff --git a/Mage.Sets/src/mage/sets/AssassinsCreed.java b/Mage.Sets/src/mage/sets/AssassinsCreed.java index 233facd2f17..5925cca943d 100644 --- a/Mage.Sets/src/mage/sets/AssassinsCreed.java +++ b/Mage.Sets/src/mage/sets/AssassinsCreed.java @@ -33,10 +33,10 @@ public final class AssassinsCreed extends ExpansionSet { cards.add(new SetCardInfo("Alexios, Deimos of Kosmos", 134, Rarity.UNCOMMON, mage.cards.a.AlexiosDeimosOfKosmos.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Alexios, Deimos of Kosmos", 214, Rarity.UNCOMMON, mage.cards.a.AlexiosDeimosOfKosmos.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Alexios, Deimos of Kosmos", 33, Rarity.UNCOMMON, mage.cards.a.AlexiosDeimosOfKosmos.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Altair Ibn-La'Ahad", 137, Rarity.MYTHIC, mage.cards.a.AltairIbnLaAhad.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Altair Ibn-La'Ahad", 225, Rarity.MYTHIC, mage.cards.a.AltairIbnLaAhad.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Altair Ibn-La'Ahad", 268, Rarity.MYTHIC, mage.cards.a.AltairIbnLaAhad.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Altair Ibn-La'Ahad", 45, Rarity.MYTHIC, mage.cards.a.AltairIbnLaAhad.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Altair Ibn-La'Ahad", 137, Rarity.MYTHIC, mage.cards.a.AltairIbnLaAhad.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Altair Ibn-La'Ahad", 225, Rarity.MYTHIC, mage.cards.a.AltairIbnLaAhad.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Altair Ibn-La'Ahad", 268, Rarity.MYTHIC, mage.cards.a.AltairIbnLaAhad.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Altair Ibn-La'Ahad", 45, Rarity.MYTHIC, mage.cards.a.AltairIbnLaAhad.class, NON_FULL_USE_VARIOUS)); //cards.add(new SetCardInfo("Apple of Eden, Isu Relic", 122, Rarity.MYTHIC, mage.cards.a.AppleOfEdenIsuRelic.class, NON_FULL_USE_VARIOUS)); //cards.add(new SetCardInfo("Apple of Eden, Isu Relic", 254, Rarity.MYTHIC, mage.cards.a.AppleOfEdenIsuRelic.class, NON_FULL_USE_VARIOUS)); //cards.add(new SetCardInfo("Apple of Eden, Isu Relic", 70, Rarity.MYTHIC, mage.cards.a.AppleOfEdenIsuRelic.class, NON_FULL_USE_VARIOUS)); @@ -144,7 +144,7 @@ public final class AssassinsCreed extends ExpansionSet { cards.add(new SetCardInfo("Ezio, Brash Novice", 145, Rarity.UNCOMMON, mage.cards.e.EzioBrashNovice.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Ezio, Brash Novice", 236, Rarity.UNCOMMON, mage.cards.e.EzioBrashNovice.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Ezio, Brash Novice", 55, Rarity.UNCOMMON, mage.cards.e.EzioBrashNovice.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Fall of the First Civilization", 4, Rarity.UNCOMMON, mage.cards.f.FallOfTheFirstCivilization.class)); + cards.add(new SetCardInfo("Fall of the First Civilization", 4, Rarity.UNCOMMON, mage.cards.f.FallOfTheFirstCivilization.class)); cards.add(new SetCardInfo("Fatal Push", 204, Rarity.UNCOMMON, mage.cards.f.FatalPush.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Fatal Push", 90, Rarity.UNCOMMON, mage.cards.f.FatalPush.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Fiery Islet", 112, Rarity.RARE, mage.cards.f.FieryIslet.class)); @@ -152,9 +152,9 @@ public final class AssassinsCreed extends ExpansionSet { cards.add(new SetCardInfo("Forest", 110, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Go for the Throat", 205, Rarity.UNCOMMON, mage.cards.g.GoForTheThroat.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Go for the Throat", 91, Rarity.UNCOMMON, mage.cards.g.GoForTheThroat.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Havi, the All-Father", 146, Rarity.RARE, mage.cards.h.HaviTheAllFather.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Havi, the All-Father", 237, Rarity.RARE, mage.cards.h.HaviTheAllFather.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Havi, the All-Father", 56, Rarity.RARE, mage.cards.h.HaviTheAllFather.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Havi, the All-Father", 146, Rarity.RARE, mage.cards.h.HaviTheAllFather.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Havi, the All-Father", 237, Rarity.RARE, mage.cards.h.HaviTheAllFather.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Havi, the All-Father", 56, Rarity.RARE, mage.cards.h.HaviTheAllFather.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Haystack", 175, Rarity.UNCOMMON, mage.cards.h.Haystack.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Haystack", 5, Rarity.UNCOMMON, mage.cards.h.Haystack.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Haytham Kenway", 57, Rarity.RARE, mage.cards.h.HaythamKenway.class, NON_FULL_USE_VARIOUS)); @@ -180,9 +180,9 @@ public final class AssassinsCreed extends ExpansionSet { cards.add(new SetCardInfo("Jackdaw", 167, Rarity.RARE, mage.cards.j.Jackdaw.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Jackdaw", 239, Rarity.RARE, mage.cards.j.Jackdaw.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Jackdaw", 58, Rarity.RARE, mage.cards.j.Jackdaw.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Jacob Frye", 132, Rarity.RARE, mage.cards.j.JacobFrye.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Jacob Frye", 207, Rarity.RARE, mage.cards.j.JacobFrye.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Jacob Frye", 27, Rarity.RARE, mage.cards.j.JacobFrye.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Jacob Frye", 132, Rarity.RARE, mage.cards.j.JacobFrye.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Jacob Frye", 207, Rarity.RARE, mage.cards.j.JacobFrye.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Jacob Frye", 27, Rarity.RARE, mage.cards.j.JacobFrye.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Kassandra, Eagle Bearer", 148, Rarity.MYTHIC, mage.cards.k.KassandraEagleBearer.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Kassandra, Eagle Bearer", 240, Rarity.MYTHIC, mage.cards.k.KassandraEagleBearer.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Kassandra, Eagle Bearer", 271, Rarity.MYTHIC, mage.cards.k.KassandraEagleBearer.class, NON_FULL_USE_VARIOUS)); @@ -210,8 +210,8 @@ public final class AssassinsCreed extends ExpansionSet { cards.add(new SetCardInfo("Mjolnir, Storm Hammer", 170, Rarity.RARE, mage.cards.m.MjolnirStormHammer.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mjolnir, Storm Hammer", 258, Rarity.RARE, mage.cards.m.MjolnirStormHammer.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mjolnir, Storm Hammer", 74, Rarity.RARE, mage.cards.m.MjolnirStormHammer.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Monastery Raid", 217, Rarity.UNCOMMON, mage.cards.m.MonasteryRaid.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Monastery Raid", 35, Rarity.UNCOMMON, mage.cards.m.MonasteryRaid.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Monastery Raid", 217, Rarity.UNCOMMON, mage.cards.m.MonasteryRaid.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Monastery Raid", 35, Rarity.UNCOMMON, mage.cards.m.MonasteryRaid.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mortify", 243, Rarity.UNCOMMON, mage.cards.m.Mortify.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mortify", 96, Rarity.UNCOMMON, mage.cards.m.Mortify.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mountain", 107, Rarity.LAND, mage.cards.basiclands.Mountain.class, FULL_ART_USE_VARIOUS)); @@ -268,9 +268,9 @@ public final class AssassinsCreed extends ExpansionSet { //cards.add(new SetCardInfo("Shaun & Rebecca, Agents", 152, Rarity.RARE, mage.cards.s.ShaunAndRebeccaAgents.class, NON_FULL_USE_VARIOUS)); //cards.add(new SetCardInfo("Shaun & Rebecca, Agents", 247, Rarity.RARE, mage.cards.s.ShaunAndRebeccaAgents.class, NON_FULL_USE_VARIOUS)); //cards.add(new SetCardInfo("Shaun & Rebecca, Agents", 64, Rarity.RARE, mage.cards.s.ShaunAndRebeccaAgents.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Shay Cormac", 153, Rarity.UNCOMMON, mage.cards.s.ShayCormac.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Shay Cormac", 248, Rarity.UNCOMMON, mage.cards.s.ShayCormac.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Shay Cormac", 65, Rarity.UNCOMMON, mage.cards.s.ShayCormac.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Shay Cormac", 153, Rarity.UNCOMMON, mage.cards.s.ShayCormac.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Shay Cormac", 248, Rarity.UNCOMMON, mage.cards.s.ShayCormac.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Shay Cormac", 65, Rarity.UNCOMMON, mage.cards.s.ShayCormac.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Sigurd, Jarl of Ravensthorpe", 154, Rarity.RARE, mage.cards.s.SigurdJarlOfRavensthorpe.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Sigurd, Jarl of Ravensthorpe", 249, Rarity.RARE, mage.cards.s.SigurdJarlOfRavensthorpe.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Sigurd, Jarl of Ravensthorpe", 66, Rarity.RARE, mage.cards.s.SigurdJarlOfRavensthorpe.class, NON_FULL_USE_VARIOUS)); diff --git a/Mage.Sets/src/mage/sets/BloomburrowCommander.java b/Mage.Sets/src/mage/sets/BloomburrowCommander.java index 6b5d49ca2b3..854ec0853ea 100644 --- a/Mage.Sets/src/mage/sets/BloomburrowCommander.java +++ b/Mage.Sets/src/mage/sets/BloomburrowCommander.java @@ -280,6 +280,7 @@ public final class BloomburrowCommander extends ExpansionSet { cards.add(new SetCardInfo("Swarmyard", 133, Rarity.RARE, mage.cards.s.Swarmyard.class)); cards.add(new SetCardInfo("Swarmyard Massacre", 20, Rarity.RARE, mage.cards.s.SwarmyardMassacre.class)); cards.add(new SetCardInfo("Swiftfoot Boots", 286, Rarity.UNCOMMON, mage.cards.s.SwiftfootBoots.class)); + cards.add(new SetCardInfo("Sword of the Squeak", 40, Rarity.RARE, mage.cards.s.SwordOfTheSqueak.class)); cards.add(new SetCardInfo("Swords to Plowshares", 109, Rarity.UNCOMMON, mage.cards.s.SwordsToPlowshares.class)); cards.add(new SetCardInfo("Tainted Wood", 337, Rarity.UNCOMMON, mage.cards.t.TaintedWood.class)); cards.add(new SetCardInfo("Talisman of Impulse", 287, Rarity.UNCOMMON, mage.cards.t.TalismanOfImpulse.class)); diff --git a/Mage.Sets/src/mage/sets/DoctorWho.java b/Mage.Sets/src/mage/sets/DoctorWho.java index 61e361fa3a2..7e00a80f7e3 100644 --- a/Mage.Sets/src/mage/sets/DoctorWho.java +++ b/Mage.Sets/src/mage/sets/DoctorWho.java @@ -78,10 +78,10 @@ public final class DoctorWho extends ExpansionSet { //cards.add(new SetCardInfo("Bad Wolf Bay", 569, Rarity.COMMON, mage.cards.b.BadWolfBay.class)); cards.add(new SetCardInfo("Banish to Another Universe", 13, Rarity.UNCOMMON, mage.cards.b.BanishToAnotherUniverse.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Banish to Another Universe", 618, Rarity.UNCOMMON, mage.cards.b.BanishToAnotherUniverse.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Barbara Wright", 14, Rarity.RARE, mage.cards.b.BarbaraWright.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Barbara Wright", 335, Rarity.RARE, mage.cards.b.BarbaraWright.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Barbara Wright", 619, Rarity.RARE, mage.cards.b.BarbaraWright.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Barbara Wright", 926, Rarity.RARE, mage.cards.b.BarbaraWright.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Barbara Wright", 14, Rarity.RARE, mage.cards.b.BarbaraWright.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Barbara Wright", 335, Rarity.RARE, mage.cards.b.BarbaraWright.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Barbara Wright", 619, Rarity.RARE, mage.cards.b.BarbaraWright.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Barbara Wright", 926, Rarity.RARE, mage.cards.b.BarbaraWright.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Beast Within", 228, Rarity.UNCOMMON, mage.cards.b.BeastWithin.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Beast Within", 819, Rarity.UNCOMMON, mage.cards.b.BeastWithin.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Become the Pilot", 354, Rarity.RARE, mage.cards.b.BecomeThePilot.class, NON_FULL_USE_VARIOUS)); @@ -103,8 +103,8 @@ public final class DoctorWho extends ExpansionSet { cards.add(new SetCardInfo("Blasphemous Act", 224, Rarity.RARE, mage.cards.b.BlasphemousAct.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Blasphemous Act", 472, Rarity.RARE, mage.cards.b.BlasphemousAct.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Blasphemous Act", 815, Rarity.RARE, mage.cards.b.BlasphemousAct.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Blink", 116, Rarity.RARE, mage.cards.b.Blink.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Blink", 721, Rarity.RARE, mage.cards.b.Blink.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Blink", 116, Rarity.RARE, mage.cards.b.Blink.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Blink", 721, Rarity.RARE, mage.cards.b.Blink.class, NON_FULL_USE_VARIOUS)); //cards.add(new SetCardInfo("Bowie Base One", 571, Rarity.COMMON, mage.cards.b.BowieBaseOne.class)); cards.add(new SetCardInfo("Canopy Vista", 1072, Rarity.RARE, mage.cards.c.CanopyVista.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Canopy Vista", 258, Rarity.RARE, mage.cards.c.CanopyVista.class, NON_FULL_USE_VARIOUS)); @@ -237,8 +237,8 @@ public final class DoctorWho extends ExpansionSet { cards.add(new SetCardInfo("Day of Destiny", 206, Rarity.RARE, mage.cards.d.DayOfDestiny.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Day of Destiny", 464, Rarity.RARE, mage.cards.d.DayOfDestiny.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Day of Destiny", 797, Rarity.RARE, mage.cards.d.DayOfDestiny.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Day of the Moon", 684, Rarity.RARE, mage.cards.d.DayOfTheMoon.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Day of the Moon", 79, Rarity.RARE, mage.cards.d.DayOfTheMoon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Day of the Moon", 684, Rarity.RARE, mage.cards.d.DayOfTheMoon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Day of the Moon", 79, Rarity.RARE, mage.cards.d.DayOfTheMoon.class, NON_FULL_USE_VARIOUS)); //cards.add(new SetCardInfo("Death in Heaven", 66, Rarity.RARE, mage.cards.d.DeathInHeaven.class, NON_FULL_USE_VARIOUS)); //cards.add(new SetCardInfo("Death in Heaven", 671, Rarity.RARE, mage.cards.d.DeathInHeaven.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Decaying Time Loop", 685, Rarity.UNCOMMON, mage.cards.d.DecayingTimeLoop.class, NON_FULL_USE_VARIOUS)); @@ -397,8 +397,8 @@ public final class DoctorWho extends ExpansionSet { cards.add(new SetCardInfo("Game Trail", 500, Rarity.RARE, mage.cards.g.GameTrail.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Game Trail", 875, Rarity.RARE, mage.cards.g.GameTrail.class, NON_FULL_USE_VARIOUS)); //cards.add(new SetCardInfo("Gardens of Tranquil Repose", 583, Rarity.COMMON, mage.cards.g.GardensOfTranquilRepose.class)); - //cards.add(new SetCardInfo("Genesis of the Daleks", 674, Rarity.RARE, mage.cards.g.GenesisOfTheDaleks.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Genesis of the Daleks", 69, Rarity.RARE, mage.cards.g.GenesisOfTheDaleks.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Genesis of the Daleks", 674, Rarity.RARE, mage.cards.g.GenesisOfTheDaleks.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Genesis of the Daleks", 69, Rarity.RARE, mage.cards.g.GenesisOfTheDaleks.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Glacial Fortress", 1092, Rarity.RARE, mage.cards.g.GlacialFortress.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Glacial Fortress", 285, Rarity.RARE, mage.cards.g.GlacialFortress.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Glacial Fortress", 501, Rarity.RARE, mage.cards.g.GlacialFortress.class, NON_FULL_USE_VARIOUS)); @@ -437,18 +437,18 @@ public final class DoctorWho extends ExpansionSet { cards.add(new SetCardInfo("Horizon Canopy", 878, Rarity.RARE, mage.cards.h.HorizonCanopy.class, NON_FULL_USE_VARIOUS)); //cards.add(new SetCardInfo("Hotel of Fears", 584, Rarity.COMMON, mage.cards.h.HotelOfFears.class)); //cards.add(new SetCardInfo("Human-Time Lord Meta-Crisis", 585, Rarity.COMMON, mage.cards.h.HumanTimeLordMetaCrisis.class)); - //cards.add(new SetCardInfo("Hunted by The Family", 361, Rarity.RARE, mage.cards.h.HuntedByTheFamily.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Hunted by The Family", 46, Rarity.RARE, mage.cards.h.HuntedByTheFamily.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Hunted by The Family", 651, Rarity.RARE, mage.cards.h.HuntedByTheFamily.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Hunted by The Family", 952, Rarity.RARE, mage.cards.h.HuntedByTheFamily.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hunted by The Family", 361, Rarity.RARE, mage.cards.h.HuntedByTheFamily.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hunted by The Family", 46, Rarity.RARE, mage.cards.h.HuntedByTheFamily.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hunted by The Family", 651, Rarity.RARE, mage.cards.h.HuntedByTheFamily.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hunted by The Family", 952, Rarity.RARE, mage.cards.h.HuntedByTheFamily.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Ian Chesterton", 22, Rarity.RARE, mage.cards.i.IanChesterton.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Ian Chesterton", 341, Rarity.RARE, mage.cards.i.IanChesterton.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Ian Chesterton", 627, Rarity.RARE, mage.cards.i.IanChesterton.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Ian Chesterton", 932, Rarity.RARE, mage.cards.i.IanChesterton.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Idris, Soul of the TARDIS", 1010, Rarity.RARE, mage.cards.i.IdrisSoulOfTheTardis.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Idris, Soul of the TARDIS", 135, Rarity.RARE, mage.cards.i.IdrisSoulOfTheTardis.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Idris, Soul of the TARDIS", 419, Rarity.RARE, mage.cards.i.IdrisSoulOfTheTardis.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Idris, Soul of the TARDIS", 740, Rarity.RARE, mage.cards.i.IdrisSoulOfTheTardis.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Idris, Soul of the TARDIS", 1010, Rarity.RARE, mage.cards.i.IdrisSoulOfTheTARDIS.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Idris, Soul of the TARDIS", 135, Rarity.RARE, mage.cards.i.IdrisSoulOfTheTARDIS.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Idris, Soul of the TARDIS", 419, Rarity.RARE, mage.cards.i.IdrisSoulOfTheTARDIS.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Idris, Soul of the TARDIS", 740, Rarity.RARE, mage.cards.i.IdrisSoulOfTheTARDIS.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Impending Flux", 386, Rarity.RARE, mage.cards.i.ImpendingFlux.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Impending Flux", 692, Rarity.RARE, mage.cards.i.ImpendingFlux.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Impending Flux", 87, Rarity.RARE, mage.cards.i.ImpendingFlux.class, NON_FULL_USE_VARIOUS)); @@ -537,8 +537,8 @@ public final class DoctorWho extends ExpansionSet { cards.add(new SetCardInfo("Me, the Immortal", 752, Rarity.RARE, mage.cards.m.MeTheImmortal.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Memory Worm", 695, Rarity.UNCOMMON, mage.cards.m.MemoryWorm.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Memory Worm", 90, Rarity.UNCOMMON, mage.cards.m.MemoryWorm.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Midnight Crusader Shuttle", 179, Rarity.UNCOMMON, mage.cards.m.MidnightCrusaderShuttle.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Midnight Crusader Shuttle", 784, Rarity.UNCOMMON, mage.cards.m.MidnightCrusaderShuttle.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Midnight Crusader Shuttle", 179, Rarity.UNCOMMON, mage.cards.m.MidnightCrusaderShuttle.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Midnight Crusader Shuttle", 784, Rarity.UNCOMMON, mage.cards.m.MidnightCrusaderShuttle.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mind Stone", 244, Rarity.UNCOMMON, mage.cards.m.MindStone.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mind Stone", 835, Rarity.UNCOMMON, mage.cards.m.MindStone.class, NON_FULL_USE_VARIOUS)); //cards.add(new SetCardInfo("Missy", 1022, Rarity.RARE, mage.cards.m.Missy.class, NON_FULL_USE_VARIOUS)); @@ -566,10 +566,10 @@ public final class DoctorWho extends ExpansionSet { cards.add(new SetCardInfo("Nardole, Resourceful Cyborg", 956, Rarity.RARE, mage.cards.n.NardoleResourcefulCyborg.class, NON_FULL_USE_VARIOUS)); //cards.add(new SetCardInfo("New New York", 592, Rarity.COMMON, mage.cards.n.NewNewYork.class)); //cards.add(new SetCardInfo("North Pole Research Base", 593, Rarity.COMMON, mage.cards.n.NorthPoleResearchBase.class)); - //cards.add(new SetCardInfo("Nyssa of Traken", 366, Rarity.RARE, mage.cards.n.NyssaOfTraken.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Nyssa of Traken", 51, Rarity.RARE, mage.cards.n.NyssaOfTraken.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Nyssa of Traken", 656, Rarity.RARE, mage.cards.n.NyssaOfTraken.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Nyssa of Traken", 957, Rarity.RARE, mage.cards.n.NyssaOfTraken.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Nyssa of Traken", 366, Rarity.RARE, mage.cards.n.NyssaOfTraken.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Nyssa of Traken", 51, Rarity.RARE, mage.cards.n.NyssaOfTraken.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Nyssa of Traken", 656, Rarity.RARE, mage.cards.n.NyssaOfTraken.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Nyssa of Traken", 957, Rarity.RARE, mage.cards.n.NyssaOfTraken.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Ominous Cemetery", 189, Rarity.UNCOMMON, mage.cards.o.OminousCemetery.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Ominous Cemetery", 794, Rarity.UNCOMMON, mage.cards.o.OminousCemetery.class, NON_FULL_USE_VARIOUS)); //cards.add(new SetCardInfo("Ood Sphere", 594, Rarity.COMMON, mage.cards.o.OodSphere.class)); @@ -692,10 +692,10 @@ public final class DoctorWho extends ExpansionSet { cards.add(new SetCardInfo("Run for Your Life", 154, Rarity.RARE, mage.cards.r.RunForYourLife.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Run for Your Life", 438, Rarity.RARE, mage.cards.r.RunForYourLife.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Run for Your Life", 759, Rarity.RARE, mage.cards.r.RunForYourLife.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Ryan Sinclair", 390, Rarity.RARE, mage.cards.r.RyanSinclair.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Ryan Sinclair", 699, Rarity.RARE, mage.cards.r.RyanSinclair.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Ryan Sinclair", 94, Rarity.RARE, mage.cards.r.RyanSinclair.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Ryan Sinclair", 981, Rarity.RARE, mage.cards.r.RyanSinclair.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ryan Sinclair", 390, Rarity.RARE, mage.cards.r.RyanSinclair.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ryan Sinclair", 699, Rarity.RARE, mage.cards.r.RyanSinclair.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ryan Sinclair", 94, Rarity.RARE, mage.cards.r.RyanSinclair.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ryan Sinclair", 981, Rarity.RARE, mage.cards.r.RyanSinclair.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Sally Sparrow", 1030, Rarity.RARE, mage.cards.s.SallySparrow.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Sally Sparrow", 155, Rarity.RARE, mage.cards.s.SallySparrow.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Sally Sparrow", 439, Rarity.RARE, mage.cards.s.SallySparrow.class, NON_FULL_USE_VARIOUS)); @@ -771,10 +771,10 @@ public final class DoctorWho extends ExpansionSet { cards.add(new SetCardInfo("Stormcarved Coast", 308, Rarity.RARE, mage.cards.s.StormcarvedCoast.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Stormcarved Coast", 518, Rarity.RARE, mage.cards.s.StormcarvedCoast.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Stormcarved Coast", 899, Rarity.RARE, mage.cards.s.StormcarvedCoast.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Strax, Sontaran Nurse", 1035, Rarity.RARE, mage.cards.s.StraxSontaranNurse.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Strax, Sontaran Nurse", 160, Rarity.RARE, mage.cards.s.StraxSontaranNurse.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Strax, Sontaran Nurse", 444, Rarity.RARE, mage.cards.s.StraxSontaranNurse.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Strax, Sontaran Nurse", 765, Rarity.RARE, mage.cards.s.StraxSontaranNurse.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Strax, Sontaran Nurse", 1035, Rarity.RARE, mage.cards.s.StraxSontaranNurse.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Strax, Sontaran Nurse", 160, Rarity.RARE, mage.cards.s.StraxSontaranNurse.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Strax, Sontaran Nurse", 444, Rarity.RARE, mage.cards.s.StraxSontaranNurse.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Strax, Sontaran Nurse", 765, Rarity.RARE, mage.cards.s.StraxSontaranNurse.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Sunbaked Canyon", 1110, Rarity.RARE, mage.cards.s.SunbakedCanyon.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Sunbaked Canyon", 309, Rarity.RARE, mage.cards.s.SunbakedCanyon.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Sunbaked Canyon", 519, Rarity.RARE, mage.cards.s.SunbakedCanyon.class, NON_FULL_USE_VARIOUS)); @@ -803,10 +803,10 @@ public final class DoctorWho extends ExpansionSet { cards.add(new SetCardInfo("Swamp", 201, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Swords to Plowshares", 212, Rarity.UNCOMMON, mage.cards.s.SwordsToPlowshares.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Swords to Plowshares", 803, Rarity.UNCOMMON, mage.cards.s.SwordsToPlowshares.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Sycorax Commander", 1036, Rarity.RARE, mage.cards.s.SycoraxCommander.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Sycorax Commander", 161, Rarity.RARE, mage.cards.s.SycoraxCommander.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Sycorax Commander", 445, Rarity.RARE, mage.cards.s.SycoraxCommander.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Sycorax Commander", 766, Rarity.RARE, mage.cards.s.SycoraxCommander.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sycorax Commander", 1036, Rarity.RARE, mage.cards.s.SycoraxCommander.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sycorax Commander", 161, Rarity.RARE, mage.cards.s.SycoraxCommander.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sycorax Commander", 445, Rarity.RARE, mage.cards.s.SycoraxCommander.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sycorax Commander", 766, Rarity.RARE, mage.cards.s.SycoraxCommander.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Talisman of Conviction", 247, Rarity.UNCOMMON, mage.cards.t.TalismanOfConviction.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Talisman of Conviction", 838, Rarity.UNCOMMON, mage.cards.t.TalismanOfConviction.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Talisman of Creativity", 248, Rarity.UNCOMMON, mage.cards.t.TalismanOfCreativity.class, NON_FULL_USE_VARIOUS)); @@ -874,8 +874,8 @@ public final class DoctorWho extends ExpansionSet { cards.add(new SetCardInfo("The Beast, Deathless Prince", 719, Rarity.RARE, mage.cards.t.TheBeastDeathlessPrince.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Beast, Deathless Prince", 994, Rarity.RARE, mage.cards.t.TheBeastDeathlessPrince.class, NON_FULL_USE_VARIOUS)); //cards.add(new SetCardInfo("The Cave of Skulls", 573, Rarity.COMMON, mage.cards.t.TheCaveOfSkulls.class)); - //cards.add(new SetCardInfo("The Caves of Androzani", 15, Rarity.RARE, mage.cards.t.TheCavesOfAndrozani.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Caves of Androzani", 620, Rarity.RARE, mage.cards.t.TheCavesOfAndrozani.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Caves of Androzani", 15, Rarity.RARE, mage.cards.t.TheCavesOfAndrozani.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Caves of Androzani", 620, Rarity.RARE, mage.cards.t.TheCavesOfAndrozani.class, NON_FULL_USE_VARIOUS)); //cards.add(new SetCardInfo("The Cheetah Planet", 574, Rarity.COMMON, mage.cards.t.TheCheetahPlanet.class)); //cards.add(new SetCardInfo("The Curse of Fenric", 118, Rarity.RARE, mage.cards.t.TheCurseOfFenric.class, NON_FULL_USE_VARIOUS)); //cards.add(new SetCardInfo("The Curse of Fenric", 723, Rarity.RARE, mage.cards.t.TheCurseOfFenric.class, NON_FULL_USE_VARIOUS)); @@ -887,8 +887,8 @@ public final class DoctorWho extends ExpansionSet { cards.add(new SetCardInfo("The Dalek Emperor", 406, Rarity.RARE, mage.cards.t.TheDalekEmperor.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Dalek Emperor", 725, Rarity.RARE, mage.cards.t.TheDalekEmperor.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Dalek Emperor", 997, Rarity.RARE, mage.cards.t.TheDalekEmperor.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Day of the Doctor", 121, Rarity.RARE, mage.cards.t.TheDayOfTheDoctor.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Day of the Doctor", 726, Rarity.RARE, mage.cards.t.TheDayOfTheDoctor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Day of the Doctor", 121, Rarity.RARE, mage.cards.t.TheDayOfTheDoctor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Day of the Doctor", 726, Rarity.RARE, mage.cards.t.TheDayOfTheDoctor.class, NON_FULL_USE_VARIOUS)); //cards.add(new SetCardInfo("The Dining Car", 578, Rarity.COMMON, mage.cards.t.TheDiningCar.class)); //cards.add(new SetCardInfo("The Doctor's Childhood Barn", 579, Rarity.COMMON, mage.cards.t.TheDoctorsChildhoodBarn.class)); //cards.add(new SetCardInfo("The Doctor's Tomb", 580, Rarity.COMMON, mage.cards.t.TheDoctorsTomb.class)); @@ -900,19 +900,19 @@ public final class DoctorWho extends ExpansionSet { //cards.add(new SetCardInfo("The Eighth Doctor", 410, Rarity.RARE, mage.cards.t.TheEighthDoctor.class, NON_FULL_USE_VARIOUS)); //cards.add(new SetCardInfo("The Eighth Doctor", 559, Rarity.RARE, mage.cards.t.TheEighthDoctor.class, NON_FULL_USE_VARIOUS)); //cards.add(new SetCardInfo("The Eighth Doctor", 729, Rarity.RARE, mage.cards.t.TheEighthDoctor.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Eleventh Doctor", "562z", Rarity.RARE, mage.cards.t.TheEleventhDoctor.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Eleventh Doctor", 1002, Rarity.RARE, mage.cards.t.TheEleventhDoctor.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Eleventh Doctor", 1153, Rarity.RARE, mage.cards.t.TheEleventhDoctor.class, NON_FULL_USE_VARIOUS));// - //cards.add(new SetCardInfo("The Eleventh Doctor", 125, Rarity.RARE, mage.cards.t.TheEleventhDoctor.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Eleventh Doctor", 411, Rarity.RARE, mage.cards.t.TheEleventhDoctor.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Eleventh Doctor", 562, Rarity.RARE, mage.cards.t.TheEleventhDoctor.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Eleventh Doctor", 730, Rarity.RARE, mage.cards.t.TheEleventhDoctor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Eleventh Doctor", "562z", Rarity.RARE, mage.cards.t.TheEleventhDoctor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Eleventh Doctor", 1002, Rarity.RARE, mage.cards.t.TheEleventhDoctor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Eleventh Doctor", 1153, Rarity.RARE, mage.cards.t.TheEleventhDoctor.class, NON_FULL_USE_VARIOUS));// + cards.add(new SetCardInfo("The Eleventh Doctor", 125, Rarity.RARE, mage.cards.t.TheEleventhDoctor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Eleventh Doctor", 411, Rarity.RARE, mage.cards.t.TheEleventhDoctor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Eleventh Doctor", 562, Rarity.RARE, mage.cards.t.TheEleventhDoctor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Eleventh Doctor", 730, Rarity.RARE, mage.cards.t.TheEleventhDoctor.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Eleventh Hour", 41, Rarity.RARE, mage.cards.t.TheEleventhHour.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Eleventh Hour", 646, Rarity.RARE, mage.cards.t.TheEleventhHour.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Face of Boe", 1003, Rarity.RARE, mage.cards.t.TheFaceOfBoe.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Face of Boe", 126, Rarity.RARE, mage.cards.t.TheFaceOfBoe.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Face of Boe", 412, Rarity.RARE, mage.cards.t.TheFaceOfBoe.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Face of Boe", 731, Rarity.RARE, mage.cards.t.TheFaceOfBoe.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Face of Boe", 1003, Rarity.RARE, mage.cards.t.TheFaceOfBoe.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Face of Boe", 126, Rarity.RARE, mage.cards.t.TheFaceOfBoe.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Face of Boe", 412, Rarity.RARE, mage.cards.t.TheFaceOfBoe.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Face of Boe", 731, Rarity.RARE, mage.cards.t.TheFaceOfBoe.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Fifth Doctor", "556z", Rarity.RARE, mage.cards.t.TheFifthDoctor.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Fifth Doctor", 1004, Rarity.RARE, mage.cards.t.TheFifthDoctor.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Fifth Doctor", 1147, Rarity.RARE, mage.cards.t.TheFifthDoctor.class, NON_FULL_USE_VARIOUS)); @@ -958,24 +958,24 @@ public final class DoctorWho extends ExpansionSet { cards.add(new SetCardInfo("The Girl in the Fireplace", 21, Rarity.RARE, mage.cards.t.TheGirlInTheFireplace.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Girl in the Fireplace", 626, Rarity.RARE, mage.cards.t.TheGirlInTheFireplace.class, NON_FULL_USE_VARIOUS)); //cards.add(new SetCardInfo("The Lux Foundation Library", 588, Rarity.COMMON, mage.cards.t.TheLuxFoundationLibrary.class)); - //cards.add(new SetCardInfo("The Master, Formed Anew", 1017, Rarity.RARE, mage.cards.t.TheMasterFormedAnew.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Master, Formed Anew", 1133, Rarity.RARE, mage.cards.t.TheMasterFormedAnew.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Master, Formed Anew", 143, Rarity.RARE, mage.cards.t.TheMasterFormedAnew.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Master, Formed Anew", 426, Rarity.RARE, mage.cards.t.TheMasterFormedAnew.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Master, Formed Anew", 542, Rarity.RARE, mage.cards.t.TheMasterFormedAnew.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Master, Formed Anew", 748, Rarity.RARE, mage.cards.t.TheMasterFormedAnew.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Master, Gallifrey's End", 1018, Rarity.RARE, mage.cards.t.TheMasterGallifreysEnd.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Master, Gallifrey's End", 1134, Rarity.RARE, mage.cards.t.TheMasterGallifreysEnd.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Master, Gallifrey's End", 144, Rarity.RARE, mage.cards.t.TheMasterGallifreysEnd.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Master, Gallifrey's End", 427, Rarity.RARE, mage.cards.t.TheMasterGallifreysEnd.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Master, Gallifrey's End", 543, Rarity.RARE, mage.cards.t.TheMasterGallifreysEnd.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Master, Gallifrey's End", 749, Rarity.RARE, mage.cards.t.TheMasterGallifreysEnd.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Master, Mesmerist", 1019, Rarity.RARE, mage.cards.t.TheMasterMesmerist.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Master, Mesmerist", 1135, Rarity.RARE, mage.cards.t.TheMasterMesmerist.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Master, Mesmerist", 145, Rarity.RARE, mage.cards.t.TheMasterMesmerist.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Master, Mesmerist", 428, Rarity.RARE, mage.cards.t.TheMasterMesmerist.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Master, Mesmerist", 544, Rarity.RARE, mage.cards.t.TheMasterMesmerist.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Master, Mesmerist", 750, Rarity.RARE, mage.cards.t.TheMasterMesmerist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Master, Formed Anew", 1017, Rarity.RARE, mage.cards.t.TheMasterFormedAnew.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Master, Formed Anew", 1133, Rarity.RARE, mage.cards.t.TheMasterFormedAnew.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Master, Formed Anew", 143, Rarity.RARE, mage.cards.t.TheMasterFormedAnew.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Master, Formed Anew", 426, Rarity.RARE, mage.cards.t.TheMasterFormedAnew.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Master, Formed Anew", 542, Rarity.RARE, mage.cards.t.TheMasterFormedAnew.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Master, Formed Anew", 748, Rarity.RARE, mage.cards.t.TheMasterFormedAnew.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Master, Gallifrey's End", 1018, Rarity.RARE, mage.cards.t.TheMasterGallifreysEnd.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Master, Gallifrey's End", 1134, Rarity.RARE, mage.cards.t.TheMasterGallifreysEnd.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Master, Gallifrey's End", 144, Rarity.RARE, mage.cards.t.TheMasterGallifreysEnd.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Master, Gallifrey's End", 427, Rarity.RARE, mage.cards.t.TheMasterGallifreysEnd.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Master, Gallifrey's End", 543, Rarity.RARE, mage.cards.t.TheMasterGallifreysEnd.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Master, Gallifrey's End", 749, Rarity.RARE, mage.cards.t.TheMasterGallifreysEnd.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Master, Mesmerist", 1019, Rarity.RARE, mage.cards.t.TheMasterMesmerist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Master, Mesmerist", 1135, Rarity.RARE, mage.cards.t.TheMasterMesmerist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Master, Mesmerist", 145, Rarity.RARE, mage.cards.t.TheMasterMesmerist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Master, Mesmerist", 428, Rarity.RARE, mage.cards.t.TheMasterMesmerist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Master, Mesmerist", 544, Rarity.RARE, mage.cards.t.TheMasterMesmerist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Master, Mesmerist", 750, Rarity.RARE, mage.cards.t.TheMasterMesmerist.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Master, Multiplied", 1020, Rarity.RARE, mage.cards.t.TheMasterMultiplied.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Master, Multiplied", 1136, Rarity.RARE, mage.cards.t.TheMasterMultiplied.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Master, Multiplied", 146, Rarity.RARE, mage.cards.t.TheMasterMultiplied.class, NON_FULL_USE_VARIOUS)); @@ -1001,8 +1001,8 @@ public final class DoctorWho extends ExpansionSet { //cards.add(new SetCardInfo("The Pandorica", 343, Rarity.RARE, mage.cards.t.ThePandorica.class, NON_FULL_USE_VARIOUS)); //cards.add(new SetCardInfo("The Pandorica", 630, Rarity.RARE, mage.cards.t.ThePandorica.class, NON_FULL_USE_VARIOUS)); //cards.add(new SetCardInfo("The Pandorica", 934, Rarity.RARE, mage.cards.t.ThePandorica.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Parting of the Ways", 696, Rarity.RARE, mage.cards.t.ThePartingOfTheWays.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Parting of the Ways", 91, Rarity.RARE, mage.cards.t.ThePartingOfTheWays.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Parting of the Ways", 696, Rarity.RARE, mage.cards.t.ThePartingOfTheWays.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Parting of the Ways", 91, Rarity.RARE, mage.cards.t.ThePartingOfTheWays.class, NON_FULL_USE_VARIOUS)); //cards.add(new SetCardInfo("The Pyramid of Mars", 597, Rarity.COMMON, mage.cards.t.ThePyramidOfMars.class)); cards.add(new SetCardInfo("The Rani", 1024, Rarity.RARE, mage.cards.t.TheRani.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Rani", 149, Rarity.RARE, mage.cards.t.TheRani.class, NON_FULL_USE_VARIOUS)); @@ -1017,13 +1017,13 @@ public final class DoctorWho extends ExpansionSet { cards.add(new SetCardInfo("The Second Doctor", 440, Rarity.RARE, mage.cards.t.TheSecondDoctor.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Second Doctor", 553, Rarity.RARE, mage.cards.t.TheSecondDoctor.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Second Doctor", 761, Rarity.RARE, mage.cards.t.TheSecondDoctor.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Seventh Doctor", "558z", Rarity.RARE, mage.cards.t.TheSeventhDoctor.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Seventh Doctor", 1033, Rarity.RARE, mage.cards.t.TheSeventhDoctor.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Seventh Doctor", 1149, Rarity.RARE, mage.cards.t.TheSeventhDoctor.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Seventh Doctor", 158, Rarity.RARE, mage.cards.t.TheSeventhDoctor.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Seventh Doctor", 442, Rarity.RARE, mage.cards.t.TheSeventhDoctor.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Seventh Doctor", 558, Rarity.RARE, mage.cards.t.TheSeventhDoctor.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Seventh Doctor", 763, Rarity.RARE, mage.cards.t.TheSeventhDoctor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Seventh Doctor", "558z", Rarity.RARE, mage.cards.t.TheSeventhDoctor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Seventh Doctor", 1033, Rarity.RARE, mage.cards.t.TheSeventhDoctor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Seventh Doctor", 1149, Rarity.RARE, mage.cards.t.TheSeventhDoctor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Seventh Doctor", 158, Rarity.RARE, mage.cards.t.TheSeventhDoctor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Seventh Doctor", 442, Rarity.RARE, mage.cards.t.TheSeventhDoctor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Seventh Doctor", 558, Rarity.RARE, mage.cards.t.TheSeventhDoctor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Seventh Doctor", 763, Rarity.RARE, mage.cards.t.TheSeventhDoctor.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Sixth Doctor", "557z", Rarity.RARE, mage.cards.t.TheSixthDoctor.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Sixth Doctor", 1034, Rarity.RARE, mage.cards.t.TheSixthDoctor.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Sixth Doctor", 1148, Rarity.RARE, mage.cards.t.TheSixthDoctor.class, NON_FULL_USE_VARIOUS)); @@ -1058,10 +1058,10 @@ public final class DoctorWho extends ExpansionSet { cards.add(new SetCardInfo("The Thirteenth Doctor", 448, Rarity.MYTHIC, mage.cards.t.TheThirteenthDoctor.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Thirteenth Doctor", 564, Rarity.MYTHIC, mage.cards.t.TheThirteenthDoctor.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Thirteenth Doctor", 609, Rarity.MYTHIC, mage.cards.t.TheThirteenthDoctor.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Toymaker's Trap", 375, Rarity.RARE, mage.cards.t.TheToymakersTrap.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Toymaker's Trap", 677, Rarity.RARE, mage.cards.t.TheToymakersTrap.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Toymaker's Trap", 72, Rarity.RARE, mage.cards.t.TheToymakersTrap.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Toymaker's Trap", 966, Rarity.RARE, mage.cards.t.TheToymakersTrap.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Toymaker's Trap", 375, Rarity.RARE, mage.cards.t.TheToymakersTrap.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Toymaker's Trap", 677, Rarity.RARE, mage.cards.t.TheToymakersTrap.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Toymaker's Trap", 72, Rarity.RARE, mage.cards.t.TheToymakersTrap.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Toymaker's Trap", 966, Rarity.RARE, mage.cards.t.TheToymakersTrap.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Twelfth Doctor", "563z", Rarity.RARE, mage.cards.t.TheTwelfthDoctor.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Twelfth Doctor", 1040, Rarity.RARE, mage.cards.t.TheTwelfthDoctor.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Twelfth Doctor", 1154, Rarity.RARE, mage.cards.t.TheTwelfthDoctor.class, NON_FULL_USE_VARIOUS)); @@ -1079,12 +1079,12 @@ public final class DoctorWho extends ExpansionSet { //cards.add(new SetCardInfo("The War Doctor", 452, Rarity.RARE, mage.cards.t.TheWarDoctor.class, NON_FULL_USE_VARIOUS)); //cards.add(new SetCardInfo("The War Doctor", 548, Rarity.RARE, mage.cards.t.TheWarDoctor.class, NON_FULL_USE_VARIOUS)); //cards.add(new SetCardInfo("The War Doctor", 772, Rarity.RARE, mage.cards.t.TheWarDoctor.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The War Games", 30, Rarity.RARE, mage.cards.t.TheWarGames.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The War Games", 635, Rarity.RARE, mage.cards.t.TheWarGames.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Wedding of River Song", 31, Rarity.RARE, mage.cards.t.TheWeddingOfRiverSong.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Wedding of River Song", 349, Rarity.RARE, mage.cards.t.TheWeddingOfRiverSong.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Wedding of River Song", 636, Rarity.RARE, mage.cards.t.TheWeddingOfRiverSong.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("The Wedding of River Song", 940, Rarity.RARE, mage.cards.t.TheWeddingOfRiverSong.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The War Games", 30, Rarity.RARE, mage.cards.t.TheWarGames.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The War Games", 635, Rarity.RARE, mage.cards.t.TheWarGames.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Wedding of River Song", 31, Rarity.RARE, mage.cards.t.TheWeddingOfRiverSong.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Wedding of River Song", 349, Rarity.RARE, mage.cards.t.TheWeddingOfRiverSong.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Wedding of River Song", 636, Rarity.RARE, mage.cards.t.TheWeddingOfRiverSong.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Wedding of River Song", 940, Rarity.RARE, mage.cards.t.TheWeddingOfRiverSong.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Thespian's Stage", 1122, Rarity.RARE, mage.cards.t.ThespiansStage.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Thespian's Stage", 323, Rarity.RARE, mage.cards.t.ThespiansStage.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Thespian's Stage", 531, Rarity.RARE, mage.cards.t.ThespiansStage.class, NON_FULL_USE_VARIOUS)); @@ -1133,8 +1133,8 @@ public final class DoctorWho extends ExpansionSet { cards.add(new SetCardInfo("Trenzalore Clocktower", 190, Rarity.RARE, mage.cards.t.TrenzaloreClocktower.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Trenzalore Clocktower", 463, Rarity.RARE, mage.cards.t.TrenzaloreClocktower.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Trenzalore Clocktower", 795, Rarity.RARE, mage.cards.t.TrenzaloreClocktower.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Trial of a Time Lord", 29, Rarity.RARE, mage.cards.t.TrialOfATimeLord.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Trial of a Time Lord", 634, Rarity.RARE, mage.cards.t.TrialOfATimeLord.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Trial of a Time Lord", 29, Rarity.RARE, mage.cards.t.TrialOfATimeLord.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Trial of a Time Lord", 634, Rarity.RARE, mage.cards.t.TrialOfATimeLord.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Truth or Consequences", 163, Rarity.UNCOMMON, mage.cards.t.TruthOrConsequences.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Truth or Consequences", 768, Rarity.UNCOMMON, mage.cards.t.TruthOrConsequences.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Twice Upon a Time", 61, Rarity.RARE, mage.cards.t.TwiceUponATime.class, NON_FULL_USE_VARIOUS)); @@ -1150,10 +1150,10 @@ public final class DoctorWho extends ExpansionSet { cards.add(new SetCardInfo("Vineglimmer Snarl", 329, Rarity.RARE, mage.cards.v.VineglimmerSnarl.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Vineglimmer Snarl", 532, Rarity.RARE, mage.cards.v.VineglimmerSnarl.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Vineglimmer Snarl", 920, Rarity.RARE, mage.cards.v.VineglimmerSnarl.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Vislor Turlough", 377, Rarity.RARE, mage.cards.v.VislorTurlough.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Vislor Turlough", 679, Rarity.RARE, mage.cards.v.VislorTurlough.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Vislor Turlough", 74, Rarity.RARE, mage.cards.v.VislorTurlough.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Vislor Turlough", 968, Rarity.RARE, mage.cards.v.VislorTurlough.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vislor Turlough", 377, Rarity.RARE, mage.cards.v.VislorTurlough.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vislor Turlough", 679, Rarity.RARE, mage.cards.v.VislorTurlough.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vislor Turlough", 74, Rarity.RARE, mage.cards.v.VislorTurlough.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vislor Turlough", 968, Rarity.RARE, mage.cards.v.VislorTurlough.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Vrestin, Menoptra Leader", 1042, Rarity.RARE, mage.cards.v.VrestinMenoptraLeader.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Vrestin, Menoptra Leader", 166, Rarity.RARE, mage.cards.v.VrestinMenoptraLeader.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Vrestin, Menoptra Leader", 451, Rarity.RARE, mage.cards.v.VrestinMenoptraLeader.class, NON_FULL_USE_VARIOUS)); diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java index de5f9106a60..bbcb0411918 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java @@ -21,7 +21,6 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { this.enablePlayBooster(Integer.MAX_VALUE); - //cards.add(new SetCardInfo("A-Leyline of Resonance", "A-143", Rarity.RARE, mage.cards.a.ALeylineOfResonance.class)); cards.add(new SetCardInfo("Abandoned Campground", 255, Rarity.COMMON, mage.cards.a.AbandonedCampground.class)); cards.add(new SetCardInfo("Abhorrent Oculus", 344, Rarity.MYTHIC, mage.cards.a.AbhorrentOculus.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Abhorrent Oculus", 42, Rarity.MYTHIC, mage.cards.a.AbhorrentOculus.class, NON_FULL_USE_VARIOUS)); @@ -44,18 +43,13 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Blazemire Verge", 329, Rarity.RARE, mage.cards.b.BlazemireVerge.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Bleeding Woods", 257, Rarity.COMMON, mage.cards.b.BleedingWoods.class)); cards.add(new SetCardInfo("Boilerbilges Ripper", 127, Rarity.COMMON, mage.cards.b.BoilerbilgesRipper.class)); - //cards.add(new SetCardInfo("Bottomless Pool // Locker Room", 43, Rarity.UNCOMMON, mage.cards.b.BottomlessPoolLockerRoom.class)); cards.add(new SetCardInfo("Break Down the Door", 170, Rarity.UNCOMMON, mage.cards.b.BreakDownTheDoor.class)); cards.add(new SetCardInfo("Broodspinner", 211, Rarity.UNCOMMON, mage.cards.b.Broodspinner.class)); cards.add(new SetCardInfo("Cackling Slasher", 85, Rarity.COMMON, mage.cards.c.CacklingSlasher.class)); cards.add(new SetCardInfo("Cathartic Parting", 171, Rarity.UNCOMMON, mage.cards.c.CatharticParting.class)); cards.add(new SetCardInfo("Cautious Survivor", 172, Rarity.COMMON, mage.cards.c.CautiousSurvivor.class)); - //cards.add(new SetCardInfo("Central Elevator // Promising Stairs", 336, Rarity.RARE, mage.cards.c.CentralElevatorPromisingStairs.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Central Elevator // Promising Stairs", 44, Rarity.RARE, mage.cards.c.CentralElevatorPromisingStairs.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Chainsaw", 128, Rarity.RARE, mage.cards.c.Chainsaw.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Chainsaw", 314, Rarity.RARE, mage.cards.c.Chainsaw.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Charred Foyer // Warped Space", 129, Rarity.MYTHIC, mage.cards.c.CharredFoyerWarpedSpace.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Charred Foyer // Warped Space", 340, Rarity.MYTHIC, mage.cards.c.CharredFoyerWarpedSpace.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Clammy Prowler", 45, Rarity.COMMON, mage.cards.c.ClammyProwler.class)); cards.add(new SetCardInfo("Clockwork Percussionist", 130, Rarity.COMMON, mage.cards.c.ClockworkPercussionist.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Clockwork Percussionist", 295, Rarity.COMMON, mage.cards.c.ClockworkPercussionist.class, NON_FULL_USE_VARIOUS)); @@ -66,7 +60,6 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Conductive Machete", 244, Rarity.UNCOMMON, mage.cards.c.ConductiveMachete.class)); cards.add(new SetCardInfo("Coordinated Clobbering", 173, Rarity.UNCOMMON, mage.cards.c.CoordinatedClobbering.class)); cards.add(new SetCardInfo("Cracked Skull", 88, Rarity.COMMON, mage.cards.c.CrackedSkull.class)); - //cards.add(new SetCardInfo("Creeping Peeper", 46, Rarity.COMMON, mage.cards.c.CreepingPeeper.class)); cards.add(new SetCardInfo("Cryptid Inspector", 174, Rarity.COMMON, mage.cards.c.CryptidInspector.class)); cards.add(new SetCardInfo("Cult Healer", 2, Rarity.COMMON, mage.cards.c.CultHealer.class)); cards.add(new SetCardInfo("Cursed Recording", 131, Rarity.RARE, mage.cards.c.CursedRecording.class, NON_FULL_USE_VARIOUS)); @@ -76,19 +69,13 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Cynical Loner", 89, Rarity.UNCOMMON, mage.cards.c.CynicalLoner.class)); cards.add(new SetCardInfo("Daggermaw Megalodon", 48, Rarity.COMMON, mage.cards.d.DaggermawMegalodon.class)); cards.add(new SetCardInfo("Dashing Bloodsucker", 90, Rarity.UNCOMMON, mage.cards.d.DashingBloodsucker.class)); - //cards.add(new SetCardInfo("Dazzling Theater // Prop Room", 3, Rarity.RARE, mage.cards.d.DazzlingTheaterPropRoom.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Dazzling Theater // Prop Room", 334, Rarity.RARE, mage.cards.d.DazzlingTheaterPropRoom.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Defiant Survivor", 175, Rarity.UNCOMMON, mage.cards.d.DefiantSurvivor.class)); - //cards.add(new SetCardInfo("Defiled Crypt // Cadaver Lab", 91, Rarity.UNCOMMON, mage.cards.d.DefiledCryptCadaverLab.class)); cards.add(new SetCardInfo("Demonic Counsel", 310, Rarity.RARE, mage.cards.d.DemonicCounsel.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Demonic Counsel", 92, Rarity.RARE, mage.cards.d.DemonicCounsel.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Derelict Attic // Widow's Walk", 93, Rarity.COMMON, mage.cards.d.DerelictAtticWidowsWalk.class)); cards.add(new SetCardInfo("Dissection Tools", 245, Rarity.RARE, mage.cards.d.DissectionTools.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Dissection Tools", 385, Rarity.RARE, mage.cards.d.DissectionTools.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Disturbing Mirth", 212, Rarity.UNCOMMON, mage.cards.d.DisturbingMirth.class)); cards.add(new SetCardInfo("Diversion Specialist", 132, Rarity.UNCOMMON, mage.cards.d.DiversionSpecialist.class)); - //cards.add(new SetCardInfo("Dollmaker's Shop // Porcelain Gallery", 335, Rarity.MYTHIC, mage.cards.d.DollmakersShopPorcelainGallery.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Dollmaker's Shop // Porcelain Gallery", 4, Rarity.MYTHIC, mage.cards.d.DollmakersShopPorcelainGallery.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Don't Make a Sound", 49, Rarity.COMMON, mage.cards.d.DontMakeASound.class)); cards.add(new SetCardInfo("Doomsday Excruciator", 346, Rarity.RARE, mage.cards.d.DoomsdayExcruciator.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Doomsday Excruciator", 94, Rarity.RARE, mage.cards.d.DoomsdayExcruciator.class, NON_FULL_USE_VARIOUS)); @@ -145,33 +132,24 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Floodfarm Verge", 259, Rarity.RARE, mage.cards.f.FloodfarmVerge.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Floodfarm Verge", 330, Rarity.RARE, mage.cards.f.FloodfarmVerge.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Floodpits Drowner", 59, Rarity.UNCOMMON, mage.cards.f.FloodpitsDrowner.class)); - cards.add(new SetCardInfo("Forest", 276, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Forest", 276, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Forest", 285, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Forest", 286, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Found Footage", 246, Rarity.COMMON, mage.cards.f.FoundFootage.class)); cards.add(new SetCardInfo("Frantic Strength", 179, Rarity.COMMON, mage.cards.f.FranticStrength.class)); cards.add(new SetCardInfo("Friendly Ghost", 12, Rarity.COMMON, mage.cards.f.FriendlyGhost.class)); cards.add(new SetCardInfo("Friendly Teddy", 247, Rarity.COMMON, mage.cards.f.FriendlyTeddy.class)); - //cards.add(new SetCardInfo("Funeral Room // Awakening Hall", 100, Rarity.MYTHIC, mage.cards.f.FuneralRoomAwakeningHall.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Funeral Room // Awakening Hall", 338, Rarity.MYTHIC, mage.cards.f.FuneralRoomAwakeningHall.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Get Out", 60, Rarity.UNCOMMON, mage.cards.g.GetOut.class)); cards.add(new SetCardInfo("Ghost Vacuum", 248, Rarity.RARE, mage.cards.g.GhostVacuum.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Ghost Vacuum", 326, Rarity.RARE, mage.cards.g.GhostVacuum.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Ghostly Dancers", 13, Rarity.RARE, mage.cards.g.GhostlyDancers.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Ghostly Dancers", 302, Rarity.RARE, mage.cards.g.GhostlyDancers.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Ghostly Keybearer", 61, Rarity.UNCOMMON, mage.cards.g.GhostlyKeybearer.class)); cards.add(new SetCardInfo("Give In to Violence", 101, Rarity.COMMON, mage.cards.g.GiveInToViolence.class)); - //cards.add(new SetCardInfo("Glassworks // Shattered Yard", 137, Rarity.COMMON, mage.cards.g.GlassworksShatteredYard.class)); cards.add(new SetCardInfo("Glimmer Seeker", 14, Rarity.UNCOMMON, mage.cards.g.GlimmerSeeker.class)); cards.add(new SetCardInfo("Glimmerburst", 62, Rarity.COMMON, mage.cards.g.Glimmerburst.class)); cards.add(new SetCardInfo("Glimmerlight", 249, Rarity.COMMON, mage.cards.g.Glimmerlight.class)); cards.add(new SetCardInfo("Gloomlake Verge", 260, Rarity.RARE, mage.cards.g.GloomlakeVerge.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Gloomlake Verge", 331, Rarity.RARE, mage.cards.g.GloomlakeVerge.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Grab the Prize", 138, Rarity.COMMON, mage.cards.g.GrabThePrize.class)); - //cards.add(new SetCardInfo("Grand Entryway // Elegant Rotunda", 15, Rarity.COMMON, mage.cards.g.GrandEntrywayElegantRotunda.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Grand Entryway // Elegant Rotunda", 287, Rarity.COMMON, mage.cards.g.GrandEntrywayElegantRotunda.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Grasping Longneck", 180, Rarity.COMMON, mage.cards.g.GraspingLongneck.class)); - //cards.add(new SetCardInfo("Greenhouse // Rickety Gazebo", 181, Rarity.UNCOMMON, mage.cards.g.GreenhouseRicketyGazebo.class)); cards.add(new SetCardInfo("Gremlin Tamer", 215, Rarity.UNCOMMON, mage.cards.g.GremlinTamer.class)); cards.add(new SetCardInfo("Grievous Wound", 102, Rarity.RARE, mage.cards.g.GrievousWound.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Grievous Wound", 375, Rarity.RARE, mage.cards.g.GrievousWound.class, NON_FULL_USE_VARIOUS)); @@ -191,13 +169,11 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Impossible Inferno", 140, Rarity.COMMON, mage.cards.i.ImpossibleInferno.class)); cards.add(new SetCardInfo("Infernal Phantom", 141, Rarity.UNCOMMON, mage.cards.i.InfernalPhantom.class)); cards.add(new SetCardInfo("Innocuous Rat", 103, Rarity.COMMON, mage.cards.i.InnocuousRat.class)); - //cards.add(new SetCardInfo("Inquisitive Glimmer", 217, Rarity.UNCOMMON, mage.cards.i.InquisitiveGlimmer.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Inquisitive Glimmer", 415, Rarity.UNCOMMON, mage.cards.i.InquisitiveGlimmer.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Insidious Fungus", 186, Rarity.UNCOMMON, mage.cards.i.InsidiousFungus.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Insidious Fungus", 321, Rarity.UNCOMMON, mage.cards.i.InsidiousFungus.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Intruding Soulrager", 218, Rarity.UNCOMMON, mage.cards.i.IntrudingSoulrager.class)); cards.add(new SetCardInfo("Irreverent Gremlin", 142, Rarity.UNCOMMON, mage.cards.i.IrreverentGremlin.class)); - cards.add(new SetCardInfo("Island", 273, Rarity.LAND, mage.cards.basiclands.Island.class, FULL_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Island", 273, Rarity.LAND, mage.cards.basiclands.Island.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Island", 279, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Island", 280, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Jump Scare", 17, Rarity.COMMON, mage.cards.j.JumpScare.class)); @@ -205,7 +181,7 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Kaito, Bane of Nightmares", 328, Rarity.MYTHIC, mage.cards.k.KaitoBaneOfNightmares.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Kaito, Bane of Nightmares", 354, Rarity.MYTHIC, mage.cards.k.KaitoBaneOfNightmares.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Kaito, Bane of Nightmares", 409, Rarity.MYTHIC, mage.cards.k.KaitoBaneOfNightmares.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Keys to the House", 251, Rarity.UNCOMMON, mage.cards.k.KeysToTheHouse.class)); + cards.add(new SetCardInfo("Keys to the House", 251, Rarity.UNCOMMON, mage.cards.k.KeysToTheHouse.class)); cards.add(new SetCardInfo("Killer's Mask", 104, Rarity.UNCOMMON, mage.cards.k.KillersMask.class)); cards.add(new SetCardInfo("Kona, Rescue Beastie", 187, Rarity.RARE, mage.cards.k.KonaRescueBeastie.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Kona, Rescue Beastie", 299, Rarity.RARE, mage.cards.k.KonaRescueBeastie.class, NON_FULL_USE_VARIOUS)); @@ -218,33 +194,28 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Leyline of Mutation", 382, Rarity.RARE, mage.cards.l.LeylineOfMutation.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Leyline of Resonance", 143, Rarity.RARE, mage.cards.l.LeylineOfResonance.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Leyline of Resonance", 379, Rarity.RARE, mage.cards.l.LeylineOfResonance.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Leyline of the Void", 106, Rarity.RARE, mage.cards.l.LeylineOfTheVoid.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Leyline of the Void", 376, Rarity.RARE, mage.cards.l.LeylineOfTheVoid.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Leyline of Transformation", 372, Rarity.RARE, mage.cards.l.LeylineOfTransformation.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Leyline of Transformation", 63, Rarity.RARE, mage.cards.l.LeylineOfTransformation.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Leyline of the Void", 106, Rarity.RARE, mage.cards.l.LeylineOfTheVoid.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Leyline of the Void", 376, Rarity.RARE, mage.cards.l.LeylineOfTheVoid.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Lionheart Glimmer", 19, Rarity.UNCOMMON, mage.cards.l.LionheartGlimmer.class)); cards.add(new SetCardInfo("Live or Die", 107, Rarity.UNCOMMON, mage.cards.l.LiveOrDie.class)); cards.add(new SetCardInfo("Living Phone", 20, Rarity.COMMON, mage.cards.l.LivingPhone.class)); cards.add(new SetCardInfo("Malevolent Chandelier", 252, Rarity.COMMON, mage.cards.m.MalevolentChandelier.class)); cards.add(new SetCardInfo("Manifest Dread", 189, Rarity.COMMON, mage.cards.m.ManifestDread.class)); - //cards.add(new SetCardInfo("Marina Vendrell", 221, Rarity.RARE, mage.cards.m.MarinaVendrell.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Marina Vendrell", 360, Rarity.RARE, mage.cards.m.MarinaVendrell.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Marina Vendrell", 221, Rarity.RARE, mage.cards.m.MarinaVendrell.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Marina Vendrell", 360, Rarity.RARE, mage.cards.m.MarinaVendrell.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Marina Vendrell's Grimoire", 308, Rarity.RARE, mage.cards.m.MarinaVendrellsGrimoire.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Marina Vendrell's Grimoire", 64, Rarity.RARE, mage.cards.m.MarinaVendrellsGrimoire.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Marvin, Murderous Mimic", 253, Rarity.RARE, mage.cards.m.MarvinMurderousMimic.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Marvin, Murderous Mimic", 367, Rarity.RARE, mage.cards.m.MarvinMurderousMimic.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Meat Locker // Drowned Diner", 65, Rarity.COMMON, mage.cards.m.MeatLockerDrownedDiner.class)); cards.add(new SetCardInfo("Meathook Massacre II", 108, Rarity.MYTHIC, mage.cards.m.MeathookMassacreII.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Meathook Massacre II", 293, Rarity.MYTHIC, mage.cards.m.MeathookMassacreII.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Meathook Massacre II", 311, Rarity.MYTHIC, mage.cards.m.MeathookMassacreII.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Miasma Demon", 109, Rarity.UNCOMMON, mage.cards.m.MiasmaDemon.class)); cards.add(new SetCardInfo("Midnight Mayhem", 222, Rarity.UNCOMMON, mage.cards.m.MidnightMayhem.class)); - //cards.add(new SetCardInfo("Mirror Room // Fractured Realm", 337, Rarity.MYTHIC, mage.cards.m.MirrorRoomFracturedRealm.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Mirror Room // Fractured Realm", 67, Rarity.MYTHIC, mage.cards.m.MirrorRoomFracturedRealm.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Moldering Gym // Weight Room", 190, Rarity.COMMON, mage.cards.m.MolderingGymWeightRoom.class)); - //cards.add(new SetCardInfo("Monstrous Emergence", 191, Rarity.COMMON, mage.cards.m.MonstrousEmergence.class)); cards.add(new SetCardInfo("Most Valuable Slayer", 144, Rarity.COMMON, mage.cards.m.MostValuableSlayer.class)); - cards.add(new SetCardInfo("Mountain", 275, Rarity.LAND, mage.cards.basiclands.Mountain.class, FULL_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 275, Rarity.LAND, mage.cards.basiclands.Mountain.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Mountain", 283, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mountain", 284, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Murder", 110, Rarity.COMMON, mage.cards.m.Murder.class)); @@ -266,7 +237,6 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Optimistic Scavenger", 288, Rarity.UNCOMMON, mage.cards.o.OptimisticScavenger.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Orphans of the Wheat", 22, Rarity.UNCOMMON, mage.cards.o.OrphansOfTheWheat.class)); cards.add(new SetCardInfo("Osseous Sticktwister", 112, Rarity.UNCOMMON, mage.cards.o.OsseousSticktwister.class)); - //cards.add(new SetCardInfo("Overgrown Zealot", 193, Rarity.UNCOMMON, mage.cards.o.OvergrownZealot.class)); cards.add(new SetCardInfo("Overlord of the Balemurk", 113, Rarity.MYTHIC, mage.cards.o.OverlordOfTheBalemurk.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Overlord of the Balemurk", 377, Rarity.MYTHIC, mage.cards.o.OverlordOfTheBalemurk.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Overlord of the Balemurk", 391, Rarity.MYTHIC, mage.cards.o.OverlordOfTheBalemurk.class, NON_FULL_USE_VARIOUS)); @@ -277,7 +247,7 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Overlord of the Boilerbilges", 403, Rarity.MYTHIC, mage.cards.o.OverlordOfTheBoilerbilges.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Overlord of the Floodpits", 373, Rarity.MYTHIC, mage.cards.o.OverlordOfTheFloodpits.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Overlord of the Floodpits", 389, Rarity.MYTHIC, mage.cards.o.OverlordOfTheFloodpits.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Overlord of the Floodpits", 399, Rarity.MYTHIC, mage.cards.o.OverlordOfTheFloodpits.class, FULL_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Overlord of the Floodpits", 399, Rarity.MYTHIC, mage.cards.o.OverlordOfTheFloodpits.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Overlord of the Floodpits", 68, Rarity.MYTHIC, mage.cards.o.OverlordOfTheFloodpits.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Overlord of the Hauntwoods", 194, Rarity.MYTHIC, mage.cards.o.OverlordOfTheHauntwoods.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Overlord of the Hauntwoods", 383, Rarity.MYTHIC, mage.cards.o.OverlordOfTheHauntwoods.class, NON_FULL_USE_VARIOUS)); @@ -287,7 +257,6 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Overlord of the Mistmoors", 370, Rarity.MYTHIC, mage.cards.o.OverlordOfTheMistmoors.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Overlord of the Mistmoors", 387, Rarity.MYTHIC, mage.cards.o.OverlordOfTheMistmoors.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Overlord of the Mistmoors", 397, Rarity.MYTHIC, mage.cards.o.OverlordOfTheMistmoors.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Painter's Studio // Defaced Gallery", 147, Rarity.UNCOMMON, mage.cards.p.PaintersStudioDefacedGallery.class)); cards.add(new SetCardInfo("Paranormal Analyst", 69, Rarity.UNCOMMON, mage.cards.p.ParanormalAnalyst.class)); cards.add(new SetCardInfo("Patched Plaything", 24, Rarity.UNCOMMON, mage.cards.p.PatchedPlaything.class)); cards.add(new SetCardInfo("Patchwork Beastie", 195, Rarity.UNCOMMON, mage.cards.p.PatchworkBeastie.class)); @@ -296,7 +265,7 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Peer Past the Veil", 325, Rarity.RARE, mage.cards.p.PeerPastTheVeil.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Piggy Bank", 148, Rarity.UNCOMMON, mage.cards.p.PiggyBank.class)); cards.add(new SetCardInfo("Piranha Fly", 70, Rarity.COMMON, mage.cards.p.PiranhaFly.class)); - cards.add(new SetCardInfo("Plains", 272, Rarity.LAND, mage.cards.basiclands.Plains.class, FULL_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 272, Rarity.LAND, mage.cards.basiclands.Plains.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Plains", 277, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Plains", 278, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Popular Egotist", 114, Rarity.UNCOMMON, mage.cards.p.PopularEgotist.class)); @@ -304,29 +273,20 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Pyroclasm", 149, Rarity.UNCOMMON, mage.cards.p.Pyroclasm.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Pyroclasm", 413, Rarity.UNCOMMON, mage.cards.p.Pyroclasm.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Ragged Playmate", 150, Rarity.COMMON, mage.cards.r.RaggedPlaymate.class)); - //cards.add(new SetCardInfo("Rampaging Soulrager", 151, Rarity.COMMON, mage.cards.r.RampagingSoulrager.class)); cards.add(new SetCardInfo("Raucous Carnival", 266, Rarity.COMMON, mage.cards.r.RaucousCarnival.class)); cards.add(new SetCardInfo("Razorkin Hordecaller", 152, Rarity.UNCOMMON, mage.cards.r.RazorkinHordecaller.class)); cards.add(new SetCardInfo("Razorkin Needlehead", 153, Rarity.RARE, mage.cards.r.RazorkinNeedlehead.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Razorkin Needlehead", 347, Rarity.RARE, mage.cards.r.RazorkinNeedlehead.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Razortrap Gorge", 267, Rarity.COMMON, mage.cards.r.RazortrapGorge.class)); - //cards.add(new SetCardInfo("Reluctant Role Model", 26, Rarity.RARE, mage.cards.r.ReluctantRoleModel.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Reluctant Role Model", 289, Rarity.RARE, mage.cards.r.ReluctantRoleModel.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Reluctant Role Model", 303, Rarity.RARE, mage.cards.r.ReluctantRoleModel.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Restricted Office // Lecture Hall", 227, Rarity.RARE, mage.cards.r.RestrictedOfficeLectureHall.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Restricted Office // Lecture Hall", 342, Rarity.RARE, mage.cards.r.RestrictedOfficeLectureHall.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Resurrected Cultist", 115, Rarity.COMMON, mage.cards.r.ResurrectedCultist.class)); cards.add(new SetCardInfo("Rip, Spawn Hunter", 228, Rarity.RARE, mage.cards.r.RipSpawnHunter.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Rip, Spawn Hunter", 362, Rarity.RARE, mage.cards.r.RipSpawnHunter.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Ripchain Razorkin", 154, Rarity.COMMON, mage.cards.r.RipchainRazorkin.class)); cards.add(new SetCardInfo("Rite of the Moth", 229, Rarity.UNCOMMON, mage.cards.r.RiteOfTheMoth.class)); - //cards.add(new SetCardInfo("Roaring Furnace // Steaming Sauna", 230, Rarity.RARE, mage.cards.r.RoaringFurnaceSteamingSauna.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Roaring Furnace // Steaming Sauna", 343, Rarity.RARE, mage.cards.r.RoaringFurnaceSteamingSauna.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Rootwise Survivor", 196, Rarity.UNCOMMON, mage.cards.r.RootwiseSurvivor.class)); cards.add(new SetCardInfo("Savior of the Small", 27, Rarity.UNCOMMON, mage.cards.s.SaviorOfTheSmall.class)); cards.add(new SetCardInfo("Saw", 254, Rarity.UNCOMMON, mage.cards.s.Saw.class)); cards.add(new SetCardInfo("Sawblade Skinripper", 231, Rarity.UNCOMMON, mage.cards.s.SawbladeSkinripper.class)); - //cards.add(new SetCardInfo("Say Its Name", 197, Rarity.COMMON, mage.cards.s.SayItsName.class)); cards.add(new SetCardInfo("Scorching Dragonfire", 156, Rarity.COMMON, mage.cards.s.ScorchingDragonfire.class)); cards.add(new SetCardInfo("Scrabbling Skullcrab", 71, Rarity.UNCOMMON, mage.cards.s.ScrabblingSkullcrab.class)); cards.add(new SetCardInfo("Screaming Nemesis", 157, Rarity.MYTHIC, mage.cards.s.ScreamingNemesis.class, NON_FULL_USE_VARIOUS)); @@ -342,7 +302,6 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Silent Hallcreeper", 72, Rarity.RARE, mage.cards.s.SilentHallcreeper.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Skullsnap Nuisance", 234, Rarity.UNCOMMON, mage.cards.s.SkullsnapNuisance.class)); cards.add(new SetCardInfo("Slavering Branchsnapper", 198, Rarity.COMMON, mage.cards.s.SlaveringBranchsnapper.class)); - //cards.add(new SetCardInfo("Smoky Lounge // Misty Salon", 235, Rarity.UNCOMMON, mage.cards.s.SmokyLoungeMistySalon.class)); cards.add(new SetCardInfo("Spectral Snatcher", 116, Rarity.COMMON, mage.cards.s.SpectralSnatcher.class)); cards.add(new SetCardInfo("Spineseeker Centipede", 199, Rarity.COMMON, mage.cards.s.SpineseekerCentipede.class)); cards.add(new SetCardInfo("Split Up", 304, Rarity.RARE, mage.cards.s.SplitUp.class, NON_FULL_USE_VARIOUS)); @@ -353,8 +312,7 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Stay Hidden, Stay Silent", 291, Rarity.UNCOMMON, mage.cards.s.StayHiddenStaySilent.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Stay Hidden, Stay Silent", 74, Rarity.UNCOMMON, mage.cards.s.StayHiddenStaySilent.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Strangled Cemetery", 268, Rarity.COMMON, mage.cards.s.StrangledCemetery.class)); - //cards.add(new SetCardInfo("Surgical Suite // Hospital Room", 34, Rarity.UNCOMMON, mage.cards.s.SurgicalSuiteHospitalRoom.class)); - cards.add(new SetCardInfo("Swamp", 274, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_USE_VARIOUS)); + cards.add(new SetCardInfo("Swamp", 274, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Swamp", 281, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Swamp", 282, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Terramorphic Expanse", 269, Rarity.COMMON, mage.cards.t.TerramorphicExpanse.class)); @@ -375,11 +333,10 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Thornspire Verge", 270, Rarity.RARE, mage.cards.t.ThornspireVerge.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Thornspire Verge", 333, Rarity.RARE, mage.cards.t.ThornspireVerge.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Threats Around Every Corner", 200, Rarity.UNCOMMON, mage.cards.t.ThreatsAroundEveryCorner.class)); - // cards.add(new SetCardInfo("Ticket Booth // Tunnel of Hate", 158, Rarity.COMMON, mage.cards.t.TicketBoothTunnelOfHate.class)); cards.add(new SetCardInfo("Toby, Beastie Befriender", 35, Rarity.RARE, mage.cards.t.TobyBeastieBefriender.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Toby, Beastie Befriender", 356, Rarity.RARE, mage.cards.t.TobyBeastieBefriender.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Trapped in the Screen", 36, Rarity.COMMON, mage.cards.t.TrappedInTheScreen.class)); - //cards.add(new SetCardInfo("Trial of Agony", 159, Rarity.UNCOMMON, mage.cards.t.TrialOfAgony.class)); + cards.add(new SetCardInfo("Trial of Agony", 159, Rarity.UNCOMMON, mage.cards.t.TrialOfAgony.class)); cards.add(new SetCardInfo("Tunnel Surveyor", 76, Rarity.COMMON, mage.cards.t.TunnelSurveyor.class)); cards.add(new SetCardInfo("Turn Inside Out", 160, Rarity.COMMON, mage.cards.t.TurnInsideOut.class)); cards.add(new SetCardInfo("Twist Reality", 77, Rarity.COMMON, mage.cards.t.TwistReality.class)); @@ -390,13 +347,8 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Tyvar, the Pummeler", 353, Rarity.MYTHIC, mage.cards.t.TyvarThePummeler.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Tyvar, the Pummeler", 408, Rarity.MYTHIC, mage.cards.t.TyvarThePummeler.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Unable to Scream", 78, Rarity.COMMON, mage.cards.u.UnableToScream.class)); - //cards.add(new SetCardInfo("Undead Sprinter", 237, Rarity.RARE, mage.cards.u.UndeadSprinter.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Undead Sprinter", 350, Rarity.RARE, mage.cards.u.UndeadSprinter.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Under the Skin", 203, Rarity.UNCOMMON, mage.cards.u.UnderTheSkin.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Under the Skin", 323, Rarity.UNCOMMON, mage.cards.u.UnderTheSkin.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Underwater Tunnel // Slimy Aquarium", 79, Rarity.COMMON, mage.cards.u.UnderwaterTunnelSlimyAquarium.class)); - //cards.add(new SetCardInfo("Unholy Annex // Ritual Chamber", 118, Rarity.RARE, mage.cards.u.UnholyAnnexRitualChamber.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Unholy Annex // Ritual Chamber", 339, Rarity.RARE, mage.cards.u.UnholyAnnexRitualChamber.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Unidentified Hovership", 305, Rarity.RARE, mage.cards.u.UnidentifiedHovership.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Unidentified Hovership", 37, Rarity.RARE, mage.cards.u.UnidentifiedHovership.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Unnerving Grasp", 80, Rarity.UNCOMMON, mage.cards.u.UnnervingGrasp.class)); @@ -414,9 +366,7 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Valgavoth's Lair", 327, Rarity.RARE, mage.cards.v.ValgavothsLair.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Valgavoth's Onslaught", 204, Rarity.RARE, mage.cards.v.ValgavothsOnslaught.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Valgavoth's Onslaught", 324, Rarity.RARE, mage.cards.v.ValgavothsOnslaught.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Valgavoth, Terror Eater", 120, Rarity.MYTHIC, mage.cards.v.ValgavothTerrorEater.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Valgavoth, Terror Eater", 352, Rarity.MYTHIC, mage.cards.v.ValgavothTerrorEater.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Valgavoth, Terror Eater", 407, Rarity.MYTHIC, mage.cards.v.ValgavothTerrorEater.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Valgavoth, Terror Eater", 120, Rarity.MYTHIC, mage.cards.v.ValgavothTerrorEater.class)); cards.add(new SetCardInfo("Vanish from Sight", 82, Rarity.COMMON, mage.cards.v.VanishFromSight.class)); cards.add(new SetCardInfo("Vengeful Possession", 162, Rarity.UNCOMMON, mage.cards.v.VengefulPossession.class)); cards.add(new SetCardInfo("Veteran Survivor", 40, Rarity.UNCOMMON, mage.cards.v.VeteranSurvivor.class)); @@ -425,8 +375,6 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Victor, Valgavoth's Seneschal", 364, Rarity.RARE, mage.cards.v.VictorValgavothsSeneschal.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Vile Mutilator", 122, Rarity.UNCOMMON, mage.cards.v.VileMutilator.class)); cards.add(new SetCardInfo("Violent Urge", 164, Rarity.UNCOMMON, mage.cards.v.ViolentUrge.class)); - //cards.add(new SetCardInfo("Walk-In Closet // Forgotten Cellar", 205, Rarity.MYTHIC, mage.cards.w.WalkInClosetForgottenCellar.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Walk-In Closet // Forgotten Cellar", 341, Rarity.MYTHIC, mage.cards.w.WalkInClosetForgottenCellar.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Waltz of Rage", 165, Rarity.RARE, mage.cards.w.WaltzOfRage.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Waltz of Rage", 318, Rarity.RARE, mage.cards.w.WaltzOfRage.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Wary Watchdog", 206, Rarity.COMMON, mage.cards.w.WaryWatchdog.class)); diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java index 427c05d2e0c..7a2f0b977f2 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java @@ -23,6 +23,7 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet { cards.add(new SetCardInfo("Aesi, Tyrant of Gyre Strait", 210, Rarity.MYTHIC, mage.cards.a.AesiTyrantOfGyreStrait.class)); cards.add(new SetCardInfo("Aether Gale", 109, Rarity.RARE, mage.cards.a.AetherGale.class)); cards.add(new SetCardInfo("Aminatou's Augury", 71, Rarity.RARE, mage.cards.a.AminatousAugury.class)); + cards.add(new SetCardInfo("Ancient Cellarspawn", 16, Rarity.RARE, mage.cards.a.AncientCellarspawn.class)); cards.add(new SetCardInfo("Arachnogenesis", 169, Rarity.RARE, mage.cards.a.Arachnogenesis.class)); cards.add(new SetCardInfo("Arcane Denial", 110, Rarity.COMMON, mage.cards.a.ArcaneDenial.class)); cards.add(new SetCardInfo("Arcane Sanctum", 259, Rarity.UNCOMMON, mage.cards.a.ArcaneSanctum.class)); @@ -38,6 +39,7 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet { cards.add(new SetCardInfo("Auramancer", 97, Rarity.COMMON, mage.cards.a.Auramancer.class)); cards.add(new SetCardInfo("Azorius Chancery", 261, Rarity.UNCOMMON, mage.cards.a.AzoriusChancery.class)); cards.add(new SetCardInfo("Azorius Signet", 240, Rarity.UNCOMMON, mage.cards.a.AzoriusSignet.class)); + cards.add(new SetCardInfo("Barbflare Gremlin", 26, Rarity.RARE, mage.cards.b.BarbflareGremlin.class)); cards.add(new SetCardInfo("Barren Moor", 262, Rarity.UNCOMMON, mage.cards.b.BarrenMoor.class)); cards.add(new SetCardInfo("Basilisk Collar", 241, Rarity.RARE, mage.cards.b.BasiliskCollar.class)); cards.add(new SetCardInfo("Bastion of Remembrance", 131, Rarity.UNCOMMON, mage.cards.b.BastionOfRemembrance.class)); @@ -88,6 +90,7 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet { cards.add(new SetCardInfo("Deluge of Doom", 18, Rarity.RARE, mage.cards.d.DelugeOfDoom.class)); cards.add(new SetCardInfo("Demolisher Spawn", 31, Rarity.RARE, mage.cards.d.DemolisherSpawn.class)); cards.add(new SetCardInfo("Demon of Fate's Design", 137, Rarity.RARE, mage.cards.d.DemonOfFatesDesign.class)); + cards.add(new SetCardInfo("Demonic Covenant", 19, Rarity.RARE, mage.cards.d.DemonicCovenant.class)); cards.add(new SetCardInfo("Diabolic Vision", 87, Rarity.UNCOMMON, mage.cards.d.DiabolicVision.class)); cards.add(new SetCardInfo("Dig Through Time", 115, Rarity.RARE, mage.cards.d.DigThroughTime.class)); cards.add(new SetCardInfo("Dimir Aqueduct", 270, Rarity.UNCOMMON, mage.cards.d.DimirAqueduct.class)); @@ -145,6 +148,7 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet { cards.add(new SetCardInfo("Infernal Grasp", 143, Rarity.UNCOMMON, mage.cards.i.InfernalGrasp.class)); cards.add(new SetCardInfo("Inkshield", 221, Rarity.RARE, mage.cards.i.Inkshield.class)); cards.add(new SetCardInfo("Inscription of Abundance", 186, Rarity.RARE, mage.cards.i.InscriptionOfAbundance.class)); + cards.add(new SetCardInfo("Into the Pit", 20, Rarity.RARE, mage.cards.i.IntoThePit.class)); cards.add(new SetCardInfo("Ishkanah, Grafwidow", 187, Rarity.MYTHIC, mage.cards.i.IshkanahGrafwidow.class)); cards.add(new SetCardInfo("Jungle Hollow", 285, Rarity.COMMON, mage.cards.j.JungleHollow.class)); cards.add(new SetCardInfo("Kaervek the Merciless", 222, Rarity.RARE, mage.cards.k.KaervekTheMerciless.class)); @@ -218,6 +222,7 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet { cards.add(new SetCardInfo("Rendmaw, Creaking Nest", 5, Rarity.MYTHIC, mage.cards.r.RendmawCreakingNest.class)); cards.add(new SetCardInfo("Retreat to Coralhelm", 126, Rarity.UNCOMMON, mage.cards.r.RetreatToCoralhelm.class)); cards.add(new SetCardInfo("Return to Dust", 102, Rarity.UNCOMMON, mage.cards.r.ReturnToDust.class)); + cards.add(new SetCardInfo("Sadistic Shell Game", 24, Rarity.RARE, mage.cards.s.SadisticShellGame.class)); cards.add(new SetCardInfo("Sakura-Tribe Elder", 194, Rarity.COMMON, mage.cards.s.SakuraTribeElder.class)); cards.add(new SetCardInfo("Sandwurm Convergence", 195, Rarity.RARE, mage.cards.s.SandwurmConvergence.class)); cards.add(new SetCardInfo("Scavenging Ooze", 196, Rarity.RARE, mage.cards.s.ScavengingOoze.class)); @@ -236,12 +241,14 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet { cards.add(new SetCardInfo("Skaab Ruinator", 128, Rarity.MYTHIC, mage.cards.s.SkaabRuinator.class)); cards.add(new SetCardInfo("Skola Grovedancer", 199, Rarity.COMMON, mage.cards.s.SkolaGrovedancer.class)); cards.add(new SetCardInfo("Smoldering Marsh", 299, Rarity.RARE, mage.cards.s.SmolderingMarsh.class)); + cards.add(new SetCardInfo("Soaring Lightbringer", 11, Rarity.RARE, mage.cards.s.SoaringLightbringer.class)); cards.add(new SetCardInfo("Sol Ring", 94, Rarity.UNCOMMON, mage.cards.s.SolRing.class)); cards.add(new SetCardInfo("Solemn Simulacrum", 253, Rarity.RARE, mage.cards.s.SolemnSimulacrum.class)); cards.add(new SetCardInfo("Sphere of Safety", 104, Rarity.UNCOMMON, mage.cards.s.SphereOfSafety.class)); cards.add(new SetCardInfo("Spinerock Knoll", 300, Rarity.RARE, mage.cards.s.SpinerockKnoll.class)); cards.add(new SetCardInfo("Spirit-Sister's Call", 232, Rarity.MYTHIC, mage.cards.s.SpiritSistersCall.class)); cards.add(new SetCardInfo("Spiteful Visions", 233, Rarity.RARE, mage.cards.s.SpitefulVisions.class)); + cards.add(new SetCardInfo("Star Athlete", 29, Rarity.RARE, mage.cards.s.StarAthlete.class)); cards.add(new SetCardInfo("Starfield Mystic", 105, Rarity.RARE, mage.cards.s.StarfieldMystic.class)); cards.add(new SetCardInfo("Stitcher's Supplier", 157, Rarity.UNCOMMON, mage.cards.s.StitchersSupplier.class)); cards.add(new SetCardInfo("Stormfist Crusader", 234, Rarity.RARE, mage.cards.s.StormfistCrusader.class)); diff --git a/Mage.Sets/src/mage/sets/Fallout.java b/Mage.Sets/src/mage/sets/Fallout.java index cb256341aae..0cf9a4160b3 100644 --- a/Mage.Sets/src/mage/sets/Fallout.java +++ b/Mage.Sets/src/mage/sets/Fallout.java @@ -680,10 +680,10 @@ public final class Fallout extends ExpansionSet { cards.add(new SetCardInfo("Pre-War Formalwear", 369, Rarity.RARE, mage.cards.p.PreWarFormalwear.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Pre-War Formalwear", 549, Rarity.RARE, mage.cards.p.PreWarFormalwear.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Pre-War Formalwear", 897, Rarity.RARE, mage.cards.p.PreWarFormalwear.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Preston Garvey, Minuteman", 425, Rarity.MYTHIC, mage.cards.p.PrestonGarveyMinuteman.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Preston Garvey, Minuteman", 536, Rarity.MYTHIC, mage.cards.p.PrestonGarveyMinuteman.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Preston Garvey, Minuteman", 8, Rarity.MYTHIC, mage.cards.p.PrestonGarveyMinuteman.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Preston Garvey, Minuteman", 953, Rarity.MYTHIC, mage.cards.p.PrestonGarveyMinuteman.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Preston Garvey, Minuteman", 425, Rarity.MYTHIC, mage.cards.p.PrestonGarveyMinuteman.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Preston Garvey, Minuteman", 536, Rarity.MYTHIC, mage.cards.p.PrestonGarveyMinuteman.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Preston Garvey, Minuteman", 8, Rarity.MYTHIC, mage.cards.p.PrestonGarveyMinuteman.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Preston Garvey, Minuteman", 953, Rarity.MYTHIC, mage.cards.p.PrestonGarveyMinuteman.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Puresteel Paladin", 170, Rarity.RARE, mage.cards.p.PuresteelPaladin.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Puresteel Paladin", 456, Rarity.RARE, mage.cards.p.PuresteelPaladin.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Puresteel Paladin", 698, Rarity.RARE, mage.cards.p.PuresteelPaladin.class, NON_FULL_USE_VARIOUS)); @@ -986,10 +986,10 @@ public final class Fallout extends ExpansionSet { cards.add(new SetCardInfo("Thirst for Knowledge", 708, Rarity.UNCOMMON, mage.cards.t.ThirstForKnowledge.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Thought Vessel", 251, Rarity.COMMON, mage.cards.t.ThoughtVessel.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Thought Vessel", 779, Rarity.COMMON, mage.cards.t.ThoughtVessel.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Three Dog, Galaxy News DJ", 120, Rarity.RARE, mage.cards.t.ThreeDogGalaxyNewsDJ.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Three Dog, Galaxy News DJ", 430, Rarity.RARE, mage.cards.t.ThreeDogGalaxyNewsDJ.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Three Dog, Galaxy News DJ", 648, Rarity.RARE, mage.cards.t.ThreeDogGalaxyNewsDJ.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Three Dog, Galaxy News DJ", 958, Rarity.RARE, mage.cards.t.ThreeDogGalaxyNewsDJ.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Three Dog, Galaxy News DJ", 120, Rarity.RARE, mage.cards.t.ThreeDogGalaxyNewsDJ.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Three Dog, Galaxy News DJ", 430, Rarity.RARE, mage.cards.t.ThreeDogGalaxyNewsDJ.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Three Dog, Galaxy News DJ", 648, Rarity.RARE, mage.cards.t.ThreeDogGalaxyNewsDJ.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Three Dog, Galaxy News DJ", 958, Rarity.RARE, mage.cards.t.ThreeDogGalaxyNewsDJ.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Thrill-Kill Disciple", 394, Rarity.RARE, mage.cards.t.ThrillKillDisciple.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Thrill-Kill Disciple", 596, Rarity.RARE, mage.cards.t.ThrillKillDisciple.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Thrill-Kill Disciple", 68, Rarity.RARE, mage.cards.t.ThrillKillDisciple.class, NON_FULL_USE_VARIOUS)); @@ -1016,12 +1016,12 @@ public final class Fallout extends ExpansionSet { cards.add(new SetCardInfo("Vault 101: Birthday Party", 556, Rarity.RARE, mage.cards.v.Vault101BirthdayParty.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Vault 112: Sadistic Simulation", 123, Rarity.RARE, mage.cards.v.Vault112SadisticSimulation.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Vault 112: Sadistic Simulation", 651, Rarity.RARE, mage.cards.v.Vault112SadisticSimulation.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Vault 11: Voter's Dilemma", 121, Rarity.RARE, mage.cards.v.Vault11VotersDilemma.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Vault 11: Voter's Dilemma", 649, Rarity.RARE, mage.cards.v.Vault11VotersDilemma.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vault 11: Voter's Dilemma", 121, Rarity.RARE, mage.cards.v.Vault11VotersDilemma.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vault 11: Voter's Dilemma", 649, Rarity.RARE, mage.cards.v.Vault11VotersDilemma.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Vault 12: The Necropolis", 51, Rarity.RARE, mage.cards.v.Vault12TheNecropolis.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Vault 12: The Necropolis", 579, Rarity.RARE, mage.cards.v.Vault12TheNecropolis.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Vault 13: Dweller's Journey", 26, Rarity.RARE, mage.cards.v.Vault13DwellersJourney.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Vault 13: Dweller's Journey", 554, Rarity.RARE, mage.cards.v.Vault13DwellersJourney.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vault 13: Dweller's Journey", 26, Rarity.RARE, mage.cards.v.Vault13DwellersJourney.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vault 13: Dweller's Journey", 554, Rarity.RARE, mage.cards.v.Vault13DwellersJourney.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Vault 21: House Gambit", 597, Rarity.RARE, mage.cards.v.Vault21HouseGambit.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Vault 21: House Gambit", 69, Rarity.RARE, mage.cards.v.Vault21HouseGambit.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Vault 75: Middle School", 27, Rarity.RARE, mage.cards.v.Vault75MiddleSchool.class, NON_FULL_USE_VARIOUS)); diff --git a/Mage.Sets/src/mage/sets/FinalFantasy.java b/Mage.Sets/src/mage/sets/FinalFantasy.java index 4e01374f3d6..eeb23735f87 100644 --- a/Mage.Sets/src/mage/sets/FinalFantasy.java +++ b/Mage.Sets/src/mage/sets/FinalFantasy.java @@ -18,24 +18,351 @@ public final class FinalFantasy extends ExpansionSet { private FinalFantasy() { super("Final Fantasy", "FIN", ExpansionSet.buildDate(2025, 6, 13), SetType.EXPANSION); this.blockName = "Final Fantasy"; // for sorting in GUI - this.hasBasicLands = false; // temporary + this.hasBasicLands = true; - cards.add(new SetCardInfo("Chaos, the Endless", 221, Rarity.UNCOMMON, mage.cards.c.ChaosTheEndless.class)); - cards.add(new SetCardInfo("Cloud, Planet's Champion", 552, Rarity.MYTHIC, mage.cards.c.CloudPlanetsChampion.class)); + cards.add(new SetCardInfo("A Realm Reborn", 196, Rarity.RARE, mage.cards.a.ARealmReborn.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("A Realm Reborn", 344, Rarity.RARE, mage.cards.a.ARealmReborn.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Absolute Virtue", 212, Rarity.MYTHIC, mage.cards.a.AbsoluteVirtue.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Absolute Virtue", 476, Rarity.MYTHIC, mage.cards.a.AbsoluteVirtue.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Adventurer's Inn", 271, Rarity.COMMON, mage.cards.a.AdventurersInn.class)); + cards.add(new SetCardInfo("Aerith Gainsborough", 374, Rarity.RARE, mage.cards.a.AerithGainsborough.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Aerith Gainsborough", 4, Rarity.RARE, mage.cards.a.AerithGainsborough.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Aerith Gainsborough", 423, Rarity.RARE, mage.cards.a.AerithGainsborough.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Aerith Gainsborough", 519, Rarity.RARE, mage.cards.a.AerithGainsborough.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Al Bhed Salvagers", 88, Rarity.UNCOMMON, mage.cards.a.AlBhedSalvagers.class)); + cards.add(new SetCardInfo("Ardyn, the Usurper", 315, Rarity.RARE, mage.cards.a.ArdynTheUsurper.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ardyn, the Usurper", 379, Rarity.RARE, mage.cards.a.ArdynTheUsurper.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ardyn, the Usurper", 444, Rarity.RARE, mage.cards.a.ArdynTheUsurper.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ardyn, the Usurper", 524, Rarity.RARE, mage.cards.a.ArdynTheUsurper.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ardyn, the Usurper", 89, Rarity.RARE, mage.cards.a.ArdynTheUsurper.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Auron's Inspiration", 8, Rarity.UNCOMMON, mage.cards.a.AuronsInspiration.class)); + cards.add(new SetCardInfo("Barret Wallace", 129, Rarity.UNCOMMON, mage.cards.b.BarretWallace.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Barret Wallace", 584, Rarity.UNCOMMON, mage.cards.b.BarretWallace.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bartz and Boko", 175, Rarity.RARE, mage.cards.b.BartzAndBoko.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bartz and Boko", 469, Rarity.RARE, mage.cards.b.BartzAndBoko.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Beatrix, Loyal General", 426, Rarity.RARE, mage.cards.b.BeatrixLoyalGeneral.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Beatrix, Loyal General", 554, Rarity.RARE, mage.cards.b.BeatrixLoyalGeneral.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Black Mage's Rod", 90, Rarity.COMMON, mage.cards.b.BlackMagesRod.class)); + cards.add(new SetCardInfo("Braska's Final Aeon", 104, Rarity.RARE, mage.cards.b.BraskasFinalAeon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Braska's Final Aeon", 363, Rarity.RARE, mage.cards.b.BraskasFinalAeon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Braska's Final Aeon", 448, Rarity.RARE, mage.cards.b.BraskasFinalAeon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Buster Sword", 255, Rarity.MYTHIC, mage.cards.b.BusterSword.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Buster Sword", 351, Rarity.MYTHIC, mage.cards.b.BusterSword.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Capital City", 274, Rarity.UNCOMMON, mage.cards.c.CapitalCity.class)); + cards.add(new SetCardInfo("Cecil, Dark Knight", 380, Rarity.RARE, mage.cards.c.CecilDarkKnight.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cecil, Dark Knight", 445, Rarity.RARE, mage.cards.c.CecilDarkKnight.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cecil, Dark Knight", 525, Rarity.RARE, mage.cards.c.CecilDarkKnight.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cecil, Dark Knight", 91, Rarity.RARE, mage.cards.c.CecilDarkKnight.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cecil, Redeemed Paladin", 380, Rarity.RARE, mage.cards.c.CecilRedeemedPaladin.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cecil, Redeemed Paladin", 445, Rarity.RARE, mage.cards.c.CecilRedeemedPaladin.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cecil, Redeemed Paladin", 525, Rarity.RARE, mage.cards.c.CecilRedeemedPaladin.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cecil, Redeemed Paladin", 91, Rarity.RARE, mage.cards.c.CecilRedeemedPaladin.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Chaos, the Endless", 221, Rarity.UNCOMMON, mage.cards.c.ChaosTheEndless.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Chaos, the Endless", 486, Rarity.UNCOMMON, mage.cards.c.ChaosTheEndless.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Choco, Seeker of Paradise", 215, Rarity.RARE, mage.cards.c.ChocoSeekerOfParadise.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Choco, Seeker of Paradise", 479, Rarity.RARE, mage.cards.c.ChocoSeekerOfParadise.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cid, Timeless Artificer", 216, Rarity.UNCOMMON, mage.cards.c.CidTimelessArtificer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cid, Timeless Artificer", 407, Rarity.UNCOMMON, mage.cards.c.CidTimelessArtificer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cid, Timeless Artificer", 408, Rarity.UNCOMMON, mage.cards.c.CidTimelessArtificer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cid, Timeless Artificer", 409, Rarity.UNCOMMON, mage.cards.c.CidTimelessArtificer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cid, Timeless Artificer", 410, Rarity.UNCOMMON, mage.cards.c.CidTimelessArtificer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cid, Timeless Artificer", 411, Rarity.UNCOMMON, mage.cards.c.CidTimelessArtificer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cid, Timeless Artificer", 412, Rarity.UNCOMMON, mage.cards.c.CidTimelessArtificer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cid, Timeless Artificer", 413, Rarity.UNCOMMON, mage.cards.c.CidTimelessArtificer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cid, Timeless Artificer", 414, Rarity.UNCOMMON, mage.cards.c.CidTimelessArtificer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cid, Timeless Artificer", 415, Rarity.UNCOMMON, mage.cards.c.CidTimelessArtificer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cid, Timeless Artificer", 416, Rarity.UNCOMMON, mage.cards.c.CidTimelessArtificer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cid, Timeless Artificer", 417, Rarity.UNCOMMON, mage.cards.c.CidTimelessArtificer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cid, Timeless Artificer", 418, Rarity.UNCOMMON, mage.cards.c.CidTimelessArtificer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cid, Timeless Artificer", 419, Rarity.UNCOMMON, mage.cards.c.CidTimelessArtificer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cid, Timeless Artificer", 420, Rarity.UNCOMMON, mage.cards.c.CidTimelessArtificer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cid, Timeless Artificer", 480, Rarity.UNCOMMON, mage.cards.c.CidTimelessArtificer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Circle of Power", 583, Rarity.UNCOMMON, mage.cards.c.CircleOfPower.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Circle of Power", 92, Rarity.UNCOMMON, mage.cards.c.CircleOfPower.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Clive, Ifrit's Dominant", 133, Rarity.MYTHIC, mage.cards.c.CliveIfritsDominant.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Clive, Ifrit's Dominant", 318, Rarity.MYTHIC, mage.cards.c.CliveIfritsDominant.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Clive, Ifrit's Dominant", 385, Rarity.MYTHIC, mage.cards.c.CliveIfritsDominant.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Clive, Ifrit's Dominant", 458, Rarity.MYTHIC, mage.cards.c.CliveIfritsDominant.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Clive, Ifrit's Dominant", 530, Rarity.MYTHIC, mage.cards.c.CliveIfritsDominant.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cloud, Midgar Mercenary", 10, Rarity.MYTHIC, mage.cards.c.CloudMidgarMercenary.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cloud, Midgar Mercenary", 375, Rarity.MYTHIC, mage.cards.c.CloudMidgarMercenary.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cloud, Midgar Mercenary", 427, Rarity.MYTHIC, mage.cards.c.CloudMidgarMercenary.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cloud, Midgar Mercenary", 520, Rarity.MYTHIC, mage.cards.c.CloudMidgarMercenary.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cloud, Planet's Champion", 482, Rarity.MYTHIC, mage.cards.c.CloudPlanetsChampion.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cloud, Planet's Champion", 552, Rarity.MYTHIC, mage.cards.c.CloudPlanetsChampion.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cloudbound Moogle", 11, Rarity.COMMON, mage.cards.c.CloudboundMoogle.class)); + cards.add(new SetCardInfo("Coeurl", 12, Rarity.COMMON, mage.cards.c.Coeurl.class)); + cards.add(new SetCardInfo("Commune with Beavers", 182, Rarity.COMMON, mage.cards.c.CommuneWithBeavers.class)); cards.add(new SetCardInfo("Cooking Campsite", 31, Rarity.UNCOMMON, mage.cards.c.CookingCampsite.class)); - cards.add(new SetCardInfo("Garland, Knight of Cornelia", 221, Rarity.UNCOMMON, mage.cards.g.GarlandKnightOfCornelia.class)); - cards.add(new SetCardInfo("Gladiolus Amicitia", 224, Rarity.UNCOMMON, mage.cards.g.GladiolusAmicitia.class)); - cards.add(new SetCardInfo("Jumbo Cactuar", 191, Rarity.RARE, mage.cards.j.JumboCactuar.class)); + cards.add(new SetCardInfo("Crystallized Serah", 240, Rarity.RARE, mage.cards.c.CrystallizedSerah.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Crystallized Serah", 506, Rarity.RARE, mage.cards.c.CrystallizedSerah.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Dark Confidant", 334, Rarity.MYTHIC, mage.cards.d.DarkConfidant.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Dark Confidant", 94, Rarity.MYTHIC, mage.cards.d.DarkConfidant.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Deadly Embrace", 557, Rarity.RARE, mage.cards.d.DeadlyEmbrace.class)); + cards.add(new SetCardInfo("Dragoon's Lance", 17, Rarity.UNCOMMON, mage.cards.d.DragoonsLance.class)); + cards.add(new SetCardInfo("Dragoon's Wyvern", 49, Rarity.COMMON, mage.cards.d.DragoonsWyvern.class)); + cards.add(new SetCardInfo("Dreams of Laguna", 50, Rarity.COMMON, mage.cards.d.DreamsOfLaguna.class)); + cards.add(new SetCardInfo("Emet-Selch, Unsundered", 218, Rarity.MYTHIC, mage.cards.e.EmetSelchUnsundered.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Emet-Selch, Unsundered", 394, Rarity.MYTHIC, mage.cards.e.EmetSelchUnsundered.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Emet-Selch, Unsundered", 483, Rarity.MYTHIC, mage.cards.e.EmetSelchUnsundered.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Emet-Selch, Unsundered", 539, Rarity.MYTHIC, mage.cards.e.EmetSelchUnsundered.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Esper Terra", 245, Rarity.MYTHIC, mage.cards.e.EsperTerra.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Esper Terra", 323, Rarity.MYTHIC, mage.cards.e.EsperTerra.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Esper Terra", 511, Rarity.MYTHIC, mage.cards.e.EsperTerra.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Evil Reawakened", 98, Rarity.UNCOMMON, mage.cards.e.EvilReawakened.class)); + cards.add(new SetCardInfo("Fang, Fearless l'Cie", 381, Rarity.UNCOMMON, mage.cards.f.FangFearlessLCie.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Fang, Fearless l'Cie", 446, Rarity.UNCOMMON, mage.cards.f.FangFearlessLCie.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Fang, Fearless l'Cie", 526, Rarity.UNCOMMON, mage.cards.f.FangFearlessLCie.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Fang, Fearless l'Cie", 99, Rarity.UNCOMMON, mage.cards.f.FangFearlessLCie.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Fate of the Sun-Cryst", 19, Rarity.COMMON, mage.cards.f.FateOfTheSunCryst.class)); + cards.add(new SetCardInfo("Fire Magic", 136, Rarity.UNCOMMON, mage.cards.f.FireMagic.class)); + cards.add(new SetCardInfo("Firion, Wild Rose Warrior", 137, Rarity.RARE, mage.cards.f.FirionWildRoseWarrior.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Firion, Wild Rose Warrior", 386, Rarity.RARE, mage.cards.f.FirionWildRoseWarrior.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Firion, Wild Rose Warrior", 459, Rarity.RARE, mage.cards.f.FirionWildRoseWarrior.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Firion, Wild Rose Warrior", 531, Rarity.RARE, mage.cards.f.FirionWildRoseWarrior.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Forest", 306, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Forest", 307, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Forest", 308, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Forest", 576, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Freya Crescent", 138, Rarity.UNCOMMON, mage.cards.f.FreyaCrescent.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Freya Crescent", 460, Rarity.UNCOMMON, mage.cards.f.FreyaCrescent.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Galian Beast", 125, Rarity.RARE, mage.cards.g.GalianBeast.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Galian Beast", 383, Rarity.RARE, mage.cards.g.GalianBeast.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Galian Beast", 454, Rarity.RARE, mage.cards.g.GalianBeast.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Galian Beast", 528, Rarity.RARE, mage.cards.g.GalianBeast.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Galuf's Final Act", 186, Rarity.UNCOMMON, mage.cards.g.GalufsFinalAct.class)); + cards.add(new SetCardInfo("Garland, Knight of Cornelia", 221, Rarity.UNCOMMON, mage.cards.g.GarlandKnightOfCornelia.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Garland, Knight of Cornelia", 486, Rarity.UNCOMMON, mage.cards.g.GarlandKnightOfCornelia.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Gilgamesh, Master-at-Arms", 139, Rarity.RARE, mage.cards.g.GilgameshMasterAtArms.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Gilgamesh, Master-at-Arms", 338, Rarity.RARE, mage.cards.g.GilgameshMasterAtArms.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Gilgamesh, Master-at-Arms", 461, Rarity.RARE, mage.cards.g.GilgameshMasterAtArms.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Gladiolus Amicitia", 224, Rarity.UNCOMMON, mage.cards.g.GladiolusAmicitia.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Gladiolus Amicitia", 489, Rarity.UNCOMMON, mage.cards.g.GladiolusAmicitia.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hades, Sorcerer of Eld", 218, Rarity.MYTHIC, mage.cards.h.HadesSorcererOfEld.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hades, Sorcerer of Eld", 394, Rarity.MYTHIC, mage.cards.h.HadesSorcererOfEld.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hades, Sorcerer of Eld", 483, Rarity.MYTHIC, mage.cards.h.HadesSorcererOfEld.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hades, Sorcerer of Eld", 539, Rarity.MYTHIC, mage.cards.h.HadesSorcererOfEld.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ice Flan", 55, Rarity.COMMON, mage.cards.i.IceFlan.class)); + cards.add(new SetCardInfo("Ice Magic", 56, Rarity.COMMON, mage.cards.i.IceMagic.class)); + cards.add(new SetCardInfo("Ifrit, Warden of Inferno", 133, Rarity.MYTHIC, mage.cards.i.IfritWardenOfInferno.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ifrit, Warden of Inferno", 318, Rarity.MYTHIC, mage.cards.i.IfritWardenOfInferno.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ifrit, Warden of Inferno", 385, Rarity.MYTHIC, mage.cards.i.IfritWardenOfInferno.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ifrit, Warden of Inferno", 458, Rarity.MYTHIC, mage.cards.i.IfritWardenOfInferno.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ifrit, Warden of Inferno", 530, Rarity.MYTHIC, mage.cards.i.IfritWardenOfInferno.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Instant Ramen", 259, Rarity.COMMON, mage.cards.i.InstantRamen.class)); + cards.add(new SetCardInfo("Ishgard, the Holy See", 283, Rarity.RARE, mage.cards.i.IshgardTheHolySee.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ishgard, the Holy See", 310, Rarity.RARE, mage.cards.i.IshgardTheHolySee.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Island", 297, Rarity.LAND, mage.cards.basiclands.Island.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Island", 298, Rarity.LAND, mage.cards.basiclands.Island.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Island", 299, Rarity.LAND, mage.cards.basiclands.Island.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Island", 573, Rarity.LAND, mage.cards.basiclands.Island.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Item Shopkeep", 142, Rarity.COMMON, mage.cards.i.ItemShopkeep.class)); + cards.add(new SetCardInfo("Jecht, Reluctant Guardian", 104, Rarity.RARE, mage.cards.j.JechtReluctantGuardian.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Jecht, Reluctant Guardian", 363, Rarity.RARE, mage.cards.j.JechtReluctantGuardian.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Jecht, Reluctant Guardian", 448, Rarity.RARE, mage.cards.j.JechtReluctantGuardian.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Jidoor, Aristocratic Capital", 284, Rarity.RARE, mage.cards.j.JidoorAristocraticCapital.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Jidoor, Aristocratic Capital", 311, Rarity.RARE, mage.cards.j.JidoorAristocraticCapital.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Jill, Shiva's Dominant", 378, Rarity.RARE, mage.cards.j.JillShivasDominant.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Jill, Shiva's Dominant", 438, Rarity.RARE, mage.cards.j.JillShivasDominant.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Jill, Shiva's Dominant", 523, Rarity.RARE, mage.cards.j.JillShivasDominant.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Jill, Shiva's Dominant", 58, Rarity.RARE, mage.cards.j.JillShivasDominant.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Judgment Bolt", 559, Rarity.RARE, mage.cards.j.JudgmentBolt.class)); + cards.add(new SetCardInfo("Jumbo Cactuar", 191, Rarity.RARE, mage.cards.j.JumboCactuar.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Jumbo Cactuar", 343, Rarity.RARE, mage.cards.j.JumboCactuar.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Kain, Traitorous Dragoon", 105, Rarity.RARE, mage.cards.k.KainTraitorousDragoon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Kain, Traitorous Dragoon", 316, Rarity.RARE, mage.cards.k.KainTraitorousDragoon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Kain, Traitorous Dragoon", 449, Rarity.RARE, mage.cards.k.KainTraitorousDragoon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Kuja, Genome Sorcerer", 232, Rarity.RARE, mage.cards.k.KujaGenomeSorcerer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Kuja, Genome Sorcerer", 399, Rarity.RARE, mage.cards.k.KujaGenomeSorcerer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Kuja, Genome Sorcerer", 497, Rarity.RARE, mage.cards.k.KujaGenomeSorcerer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Kuja, Genome Sorcerer", 544, Rarity.RARE, mage.cards.k.KujaGenomeSorcerer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Laughing Mad", 143, Rarity.COMMON, mage.cards.l.LaughingMad.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Laughing Mad", 585, Rarity.COMMON, mage.cards.l.LaughingMad.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lightning, Security Sergeant", 462, Rarity.RARE, mage.cards.l.LightningSecuritySergeant.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lightning, Security Sergeant", 560, Rarity.RARE, mage.cards.l.LightningSecuritySergeant.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Machinist's Arsenal", 23, Rarity.RARE, mage.cards.m.MachinistsArsenal.class)); + cards.add(new SetCardInfo("Magitek Scythe", 562, Rarity.RARE, mage.cards.m.MagitekScythe.class)); + cards.add(new SetCardInfo("Malboro", 106, Rarity.COMMON, mage.cards.m.Malboro.class)); + cards.add(new SetCardInfo("Matoya, Archon Elder", 439, Rarity.RARE, mage.cards.m.MatoyaArchonElder.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Matoya, Archon Elder", 62, Rarity.RARE, mage.cards.m.MatoyaArchonElder.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Moogles' Valor", 27, Rarity.RARE, mage.cards.m.MooglesValor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Moogles' Valor", 326, Rarity.RARE, mage.cards.m.MooglesValor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 303, Rarity.LAND, mage.cards.basiclands.Mountain.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 304, Rarity.LAND, mage.cards.basiclands.Mountain.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 305, Rarity.LAND, mage.cards.basiclands.Mountain.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 575, Rarity.LAND, mage.cards.basiclands.Mountain.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Namazu Trader", 107, Rarity.COMMON, mage.cards.n.NamazuTrader.class)); + cards.add(new SetCardInfo("Nibelheim Aflame", 146, Rarity.MYTHIC, mage.cards.n.NibelheimAflame.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Nibelheim Aflame", 339, Rarity.MYTHIC, mage.cards.n.NibelheimAflame.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Paladin's Arms", 28, Rarity.COMMON, mage.cards.p.PaladinsArms.class)); + cards.add(new SetCardInfo("Phantom Train", 110, Rarity.UNCOMMON, mage.cards.p.PhantomTrain.class)); + cards.add(new SetCardInfo("Phoenix Down", 29, Rarity.UNCOMMON, mage.cards.p.PhoenixDown.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Phoenix Down", 578, Rarity.UNCOMMON, mage.cards.p.PhoenixDown.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 294, Rarity.LAND, mage.cards.basiclands.Plains.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Plains", 295, Rarity.LAND, mage.cards.basiclands.Plains.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Plains", 296, Rarity.LAND, mage.cards.basiclands.Plains.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Plains", 572, Rarity.LAND, mage.cards.basiclands.Plains.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Poison the Waters", 111, Rarity.UNCOMMON, mage.cards.p.PoisonTheWaters.class)); + cards.add(new SetCardInfo("PuPu UFO", 266, Rarity.UNCOMMON, mage.cards.p.PuPuUFO.class)); + cards.add(new SetCardInfo("Quistis Trepe", 440, Rarity.UNCOMMON, mage.cards.q.QuistisTrepe.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Quistis Trepe", 66, Rarity.UNCOMMON, mage.cards.q.QuistisTrepe.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Rabanastre, Royal City", 287, Rarity.COMMON, mage.cards.r.RabanastreRoyalCity.class)); + cards.add(new SetCardInfo("Ragnarok, Divine Deliverance", "381b", Rarity.UNCOMMON, mage.cards.r.RagnarokDivineDeliverance.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ragnarok, Divine Deliverance", "446b", Rarity.UNCOMMON, mage.cards.r.RagnarokDivineDeliverance.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ragnarok, Divine Deliverance", "526b", Rarity.UNCOMMON, mage.cards.r.RagnarokDivineDeliverance.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ragnarok, Divine Deliverance", "99b", Rarity.UNCOMMON, mage.cards.r.RagnarokDivineDeliverance.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Relm's Sketching", 67, Rarity.UNCOMMON, mage.cards.r.RelmsSketching.class)); + cards.add(new SetCardInfo("Reno and Rude", 113, Rarity.UNCOMMON, mage.cards.r.RenoAndRude.class)); + cards.add(new SetCardInfo("Retrieve the Esper", 68, Rarity.COMMON, mage.cards.r.RetrieveTheEsper.class)); + cards.add(new SetCardInfo("Rinoa Heartilly", 237, Rarity.UNCOMMON, mage.cards.r.RinoaHeartilly.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Rinoa Heartilly", 502, Rarity.UNCOMMON, mage.cards.r.RinoaHeartilly.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Rosa, Resolute White Mage", 431, Rarity.RARE, mage.cards.r.RosaResoluteWhiteMage.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Rosa, Resolute White Mage", 555, Rarity.RARE, mage.cards.r.RosaResoluteWhiteMage.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sage's Nouliths", 582, Rarity.COMMON, mage.cards.s.SagesNouliths.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sage's Nouliths", 70, Rarity.COMMON, mage.cards.s.SagesNouliths.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sahagin", 71, Rarity.COMMON, mage.cards.s.Sahagin.class)); + cards.add(new SetCardInfo("Samurai's Katana", 154, Rarity.UNCOMMON, mage.cards.s.SamuraisKatana.class)); + cards.add(new SetCardInfo("Sazh Katzroy", 199, Rarity.RARE, mage.cards.s.SazhKatzroy.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sazh Katzroy", 472, Rarity.RARE, mage.cards.s.SazhKatzroy.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Sazh's Chocobo", 200, Rarity.UNCOMMON, mage.cards.s.SazhsChocobo.class)); - cards.add(new SetCardInfo("Sephiroth, Planet's Heir", 553, Rarity.MYTHIC, mage.cards.s.SephirothPlanetsHeir.class)); + cards.add(new SetCardInfo("Self-Destruct", 157, Rarity.UNCOMMON, mage.cards.s.SelfDestruct.class)); + cards.add(new SetCardInfo("Sephiroth's Intervention", 116, Rarity.COMMON, mage.cards.s.SephirothsIntervention.class)); + cards.add(new SetCardInfo("Sephiroth, Fabled SOLDIER", 115, Rarity.MYTHIC, mage.cards.s.SephirothFabledSOLDIER.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sephiroth, Fabled SOLDIER", 317, Rarity.MYTHIC, mage.cards.s.SephirothFabledSOLDIER.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sephiroth, Fabled SOLDIER", 382, Rarity.MYTHIC, mage.cards.s.SephirothFabledSOLDIER.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sephiroth, Fabled SOLDIER", 451, Rarity.MYTHIC, mage.cards.s.SephirothFabledSOLDIER.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sephiroth, Fabled SOLDIER", 527, Rarity.MYTHIC, mage.cards.s.SephirothFabledSOLDIER.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sephiroth, One-Winged Angel", 115, Rarity.MYTHIC, mage.cards.s.SephirothOneWingedAngel.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sephiroth, One-Winged Angel", 317, Rarity.MYTHIC, mage.cards.s.SephirothOneWingedAngel.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sephiroth, One-Winged Angel", 382, Rarity.MYTHIC, mage.cards.s.SephirothOneWingedAngel.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sephiroth, One-Winged Angel", 451, Rarity.MYTHIC, mage.cards.s.SephirothOneWingedAngel.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sephiroth, One-Winged Angel", 527, Rarity.MYTHIC, mage.cards.s.SephirothOneWingedAngel.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sephiroth, Planet's Heir", 505, Rarity.MYTHIC, mage.cards.s.SephirothPlanetsHeir.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sephiroth, Planet's Heir", 553, Rarity.MYTHIC, mage.cards.s.SephirothPlanetsHeir.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Serah Farron", 240, Rarity.RARE, mage.cards.s.SerahFarron.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Serah Farron", 506, Rarity.RARE, mage.cards.s.SerahFarron.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Seymour Flux", 452, Rarity.RARE, mage.cards.s.SeymourFlux.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Seymour Flux", 558, Rarity.RARE, mage.cards.s.SeymourFlux.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Shambling Cie'th", 117, Rarity.UNCOMMON, mage.cards.s.ShamblingCieth.class)); + cards.add(new SetCardInfo("Shantotto, Tactician Magician", 241, Rarity.UNCOMMON, mage.cards.s.ShantottoTacticianMagician.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Shantotto, Tactician Magician", 507, Rarity.UNCOMMON, mage.cards.s.ShantottoTacticianMagician.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Shinra Reinforcements", 118, Rarity.COMMON, mage.cards.s.ShinraReinforcements.class)); + cards.add(new SetCardInfo("Shinryu, Transcendent Rival", 127, Rarity.RARE, mage.cards.s.ShinryuTranscendentRival.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Shinryu, Transcendent Rival", 384, Rarity.RARE, mage.cards.s.ShinryuTranscendentRival.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Shinryu, Transcendent Rival", 455, Rarity.RARE, mage.cards.s.ShinryuTranscendentRival.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Shinryu, Transcendent Rival", 529, Rarity.RARE, mage.cards.s.ShinryuTranscendentRival.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Shiva, Warden of Ice", 378, Rarity.RARE, mage.cards.s.ShivaWardenOfIce.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Shiva, Warden of Ice", 438, Rarity.RARE, mage.cards.s.ShivaWardenOfIce.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Shiva, Warden of Ice", 523, Rarity.RARE, mage.cards.s.ShivaWardenOfIce.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Shiva, Warden of Ice", 58, Rarity.RARE, mage.cards.s.ShivaWardenOfIce.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Sidequest: Catch a Fish", 31, Rarity.UNCOMMON, mage.cards.s.SidequestCatchAFish.class)); cards.add(new SetCardInfo("Sin, Spira's Punishment", 242, Rarity.RARE, mage.cards.s.SinSpirasPunishment.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Sin, Spira's Punishment", 348, Rarity.RARE, mage.cards.s.SinSpirasPunishment.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Sin, Spira's Punishment", 508, Rarity.RARE, mage.cards.s.SinSpirasPunishment.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Stiltzkin, Moogle Merchant", 34, Rarity.RARE, mage.cards.s.StiltzkinMoogleMerchant.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Squall, SeeD Mercenary", 243, Rarity.RARE, mage.cards.s.SquallSeeDMercenary.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Squall, SeeD Mercenary", 402, Rarity.RARE, mage.cards.s.SquallSeeDMercenary.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Squall, SeeD Mercenary", 509, Rarity.RARE, mage.cards.s.SquallSeeDMercenary.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Squall, SeeD Mercenary", 547, Rarity.RARE, mage.cards.s.SquallSeeDMercenary.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Starting Town", 289, Rarity.RARE, mage.cards.s.StartingTown.class)); cards.add(new SetCardInfo("Stiltzkin, Moogle Merchant", 327, Rarity.RARE, mage.cards.s.StiltzkinMoogleMerchant.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Summon: Shiva", 78, Rarity.UNCOMMON, mage.cards.s.SummonShiva.class)); + cards.add(new SetCardInfo("Stiltzkin, Moogle Merchant", 34, Rarity.RARE, mage.cards.s.StiltzkinMoogleMerchant.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Stiltzkin, Moogle Merchant", 433, Rarity.RARE, mage.cards.s.StiltzkinMoogleMerchant.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Summon: Anima", 120, Rarity.UNCOMMON, mage.cards.s.SummonAnima.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Summon: Anima", 364, Rarity.UNCOMMON, mage.cards.s.SummonAnima.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Summon: Bahamut", 1, Rarity.MYTHIC, mage.cards.s.SummonBahamut.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Summon: Bahamut", 356, Rarity.MYTHIC, mage.cards.s.SummonBahamut.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Summon: Esper Ramuh", 161, Rarity.UNCOMMON, mage.cards.s.SummonEsperRamuh.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Summon: Esper Ramuh", 367, Rarity.UNCOMMON, mage.cards.s.SummonEsperRamuh.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Summon: Knights of Round", 359, Rarity.MYTHIC, mage.cards.s.SummonKnightsOfRound.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Summon: Knights of Round", 36, Rarity.MYTHIC, mage.cards.s.SummonKnightsOfRound.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Summon: Leviathan", 361, Rarity.RARE, mage.cards.s.SummonLeviathan.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Summon: Leviathan", 77, Rarity.RARE, mage.cards.s.SummonLeviathan.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Summon: Primal Garuda", 360, Rarity.UNCOMMON, mage.cards.s.SummonPrimalGaruda.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Summon: Primal Garuda", 37, Rarity.UNCOMMON, mage.cards.s.SummonPrimalGaruda.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Summon: Primal Odin", 121, Rarity.RARE, mage.cards.s.SummonPrimalOdin.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Summon: Primal Odin", 365, Rarity.RARE, mage.cards.s.SummonPrimalOdin.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Summon: Shiva", 362, Rarity.UNCOMMON, mage.cards.s.SummonShiva.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Summon: Shiva", 78, Rarity.UNCOMMON, mage.cards.s.SummonShiva.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Summoner's Grimoire", 205, Rarity.RARE, mage.cards.s.SummonersGrimoire.class)); + cards.add(new SetCardInfo("Suplex", 164, Rarity.COMMON, mage.cards.s.Suplex.class)); + cards.add(new SetCardInfo("Swamp", 300, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Swamp", 301, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Swamp", 302, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Swamp", 574, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Terra, Magical Adept", 245, Rarity.MYTHIC, mage.cards.t.TerraMagicalAdept.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Terra, Magical Adept", 323, Rarity.MYTHIC, mage.cards.t.TerraMagicalAdept.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Terra, Magical Adept", 511, Rarity.MYTHIC, mage.cards.t.TerraMagicalAdept.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Crystal's Chosen", 14, Rarity.UNCOMMON, mage.cards.t.TheCrystalsChosen.class)); + cards.add(new SetCardInfo("The Emperor of Palamecia", 219, Rarity.UNCOMMON, mage.cards.t.TheEmperorOfPalamecia.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Emperor of Palamecia", 484, Rarity.UNCOMMON, mage.cards.t.TheEmperorOfPalamecia.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Lord Master of Hell", 219, Rarity.UNCOMMON, mage.cards.t.TheLordMasterOfHell.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Lord Master of Hell", 484, Rarity.UNCOMMON, mage.cards.t.TheLordMasterOfHell.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Prima Vista", 64, Rarity.UNCOMMON, mage.cards.t.ThePrimaVista.class)); + cards.add(new SetCardInfo("Tifa Lockhart", 206, Rarity.RARE, mage.cards.t.TifaLockhart.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tifa Lockhart", 391, Rarity.RARE, mage.cards.t.TifaLockhart.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tifa Lockhart", 473, Rarity.RARE, mage.cards.t.TifaLockhart.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tifa Lockhart", 536, Rarity.RARE, mage.cards.t.TifaLockhart.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Tonberry", 122, Rarity.UNCOMMON, mage.cards.t.Tonberry.class)); - cards.add(new SetCardInfo("Zell Dincht", 170, Rarity.RARE, mage.cards.z.ZellDincht.class)); + cards.add(new SetCardInfo("Town Greeter", 209, Rarity.COMMON, mage.cards.t.TownGreeter.class)); + cards.add(new SetCardInfo("Trance Kuja, Fate Defied", 232, Rarity.RARE, mage.cards.t.TranceKujaFateDefied.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Trance Kuja, Fate Defied", 399, Rarity.RARE, mage.cards.t.TranceKujaFateDefied.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Trance Kuja, Fate Defied", 497, Rarity.RARE, mage.cards.t.TranceKujaFateDefied.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Trance Kuja, Fate Defied", 544, Rarity.RARE, mage.cards.t.TranceKujaFateDefied.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Traveling Chocobo", "551a", Rarity.MYTHIC, mage.cards.t.TravelingChocobo.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Traveling Chocobo", "551b", Rarity.MYTHIC, mage.cards.t.TravelingChocobo.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Traveling Chocobo", "551c", Rarity.MYTHIC, mage.cards.t.TravelingChocobo.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Traveling Chocobo", "551d", Rarity.MYTHIC, mage.cards.t.TravelingChocobo.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Traveling Chocobo", "551f", Rarity.MYTHIC, mage.cards.t.TravelingChocobo.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Traveling Chocobo", 210, Rarity.MYTHIC, mage.cards.t.TravelingChocobo.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Traveling Chocobo", 406, Rarity.MYTHIC, mage.cards.t.TravelingChocobo.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Traveling Chocobo", 551, Rarity.MYTHIC, mage.cards.t.TravelingChocobo.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Triple Triad", 166, Rarity.RARE, mage.cards.t.TripleTriad.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Triple Triad", 340, Rarity.RARE, mage.cards.t.TripleTriad.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ultima Weapon", 563, Rarity.RARE, mage.cards.u.UltimaWeapon.class)); + cards.add(new SetCardInfo("Ultima, Origin of Oblivion", 2, Rarity.RARE, mage.cards.u.UltimaOriginOfOblivion.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ultima, Origin of Oblivion", 324, Rarity.RARE, mage.cards.u.UltimaOriginOfOblivion.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ultima, Origin of Oblivion", 421, Rarity.RARE, mage.cards.u.UltimaOriginOfOblivion.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ultimecia, Omnipotent", 247, Rarity.UNCOMMON, mage.cards.u.UltimeciaOmnipotent.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ultimecia, Omnipotent", 513, Rarity.UNCOMMON, mage.cards.u.UltimeciaOmnipotent.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ultimecia, Temporal Threat", 441, Rarity.RARE, mage.cards.u.UltimeciaTemporalThreat.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ultimecia, Temporal Threat", 556, Rarity.RARE, mage.cards.u.UltimeciaTemporalThreat.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ultimecia, Time Sorceress", 247, Rarity.UNCOMMON, mage.cards.u.UltimeciaTimeSorceress.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ultimecia, Time Sorceress", 513, Rarity.UNCOMMON, mage.cards.u.UltimeciaTimeSorceress.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Undercity Dire Rat", 123, Rarity.COMMON, mage.cards.u.UndercityDireRat.class)); + cards.add(new SetCardInfo("Valkyrie Aerial Unit", 84, Rarity.UNCOMMON, mage.cards.v.ValkyrieAerialUnit.class)); + cards.add(new SetCardInfo("Vanille, Cheerful l'Cie", 211, Rarity.UNCOMMON, mage.cards.v.VanilleCheerfulLCie.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vanille, Cheerful l'Cie", 392, Rarity.UNCOMMON, mage.cards.v.VanilleCheerfulLCie.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vanille, Cheerful l'Cie", 475, Rarity.UNCOMMON, mage.cards.v.VanilleCheerfulLCie.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vanille, Cheerful l'Cie", 537, Rarity.UNCOMMON, mage.cards.v.VanilleCheerfulLCie.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vincent Valentine", 125, Rarity.RARE, mage.cards.v.VincentValentine.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vincent Valentine", 383, Rarity.RARE, mage.cards.v.VincentValentine.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vincent Valentine", 454, Rarity.RARE, mage.cards.v.VincentValentine.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vincent Valentine", 528, Rarity.RARE, mage.cards.v.VincentValentine.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vivi Ornitier", 248, Rarity.MYTHIC, mage.cards.v.ViviOrnitier.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vivi Ornitier", 321, Rarity.MYTHIC, mage.cards.v.ViviOrnitier.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vivi Ornitier", 514, Rarity.MYTHIC, mage.cards.v.ViviOrnitier.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Warrior's Sword", 169, Rarity.COMMON, mage.cards.w.WarriorsSword.class)); + cards.add(new SetCardInfo("Wastes", 309, Rarity.COMMON, mage.cards.w.Wastes.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("White Auracite", 41, Rarity.COMMON, mage.cards.w.WhiteAuracite.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("White Auracite", 579, Rarity.COMMON, mage.cards.w.WhiteAuracite.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("White Mage's Staff", 42, Rarity.COMMON, mage.cards.w.WhiteMagesStaff.class)); + cards.add(new SetCardInfo("Xande, Dark Mage", 516, Rarity.RARE, mage.cards.x.XandeDarkMage.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Xande, Dark Mage", 561, Rarity.RARE, mage.cards.x.XandeDarkMage.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Y'shtola Rhul", 443, Rarity.MYTHIC, mage.cards.y.YshtolaRhul.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Y'shtola Rhul", 577, Rarity.MYTHIC, mage.cards.y.YshtolaRhul.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Y'shtola Rhul", 86, Rarity.MYTHIC, mage.cards.y.YshtolaRhul.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("You're Not Alone", 44, Rarity.COMMON, mage.cards.y.YoureNotAlone.class)); + cards.add(new SetCardInfo("Yuna, Hope of Spira", 250, Rarity.MYTHIC, mage.cards.y.YunaHopeOfSpira.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Yuna, Hope of Spira", 404, Rarity.MYTHIC, mage.cards.y.YunaHopeOfSpira.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Yuna, Hope of Spira", 517, Rarity.MYTHIC, mage.cards.y.YunaHopeOfSpira.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Yuna, Hope of Spira", 549, Rarity.MYTHIC, mage.cards.y.YunaHopeOfSpira.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Zanarkand, Ancient Metropolis", 293, Rarity.RARE, mage.cards.z.ZanarkandAncientMetropolis.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Zanarkand, Ancient Metropolis", 314, Rarity.RARE, mage.cards.z.ZanarkandAncientMetropolis.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Zell Dincht", 170, Rarity.RARE, mage.cards.z.ZellDincht.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Zell Dincht", 468, Rarity.RARE, mage.cards.z.ZellDincht.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Zenos yae Galvus", 127, Rarity.RARE, mage.cards.z.ZenosYaeGalvus.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Zenos yae Galvus", 384, Rarity.RARE, mage.cards.z.ZenosYaeGalvus.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Zenos yae Galvus", 455, Rarity.RARE, mage.cards.z.ZenosYaeGalvus.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Zenos yae Galvus", 529, Rarity.RARE, mage.cards.z.ZenosYaeGalvus.class, NON_FULL_USE_VARIOUS)); } } diff --git a/Mage.Sets/src/mage/sets/FinalFantasyCommander.java b/Mage.Sets/src/mage/sets/FinalFantasyCommander.java index 618587f8498..3450f4ada24 100644 --- a/Mage.Sets/src/mage/sets/FinalFantasyCommander.java +++ b/Mage.Sets/src/mage/sets/FinalFantasyCommander.java @@ -19,17 +19,375 @@ public final class FinalFantasyCommander extends ExpansionSet { super("Final Fantasy Commander", "FIC", ExpansionSet.buildDate(2025, 6, 13), SetType.SUPPLEMENTAL); this.hasBasicLands = false; + cards.add(new SetCardInfo("Aerith, Last Ancient", 163, Rarity.RARE, mage.cards.a.AerithLastAncient.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Aerith, Last Ancient", 76, Rarity.RARE, mage.cards.a.AerithLastAncient.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Alisaie Leveilleur", 129, Rarity.RARE, mage.cards.a.AlisaieLeveilleur.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Alisaie Leveilleur", 9, Rarity.RARE, mage.cards.a.AlisaieLeveilleur.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Alphinaud Leveilleur", 140, Rarity.RARE, mage.cards.a.AlphinaudLeveilleur.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Alphinaud Leveilleur", 33, Rarity.RARE, mage.cards.a.AlphinaudLeveilleur.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Altered Ego", 317, Rarity.RARE, mage.cards.a.AlteredEgo.class)); + cards.add(new SetCardInfo("An Offer You Can't Refuse", 267, Rarity.UNCOMMON, mage.cards.a.AnOfferYouCantRefuse.class)); + cards.add(new SetCardInfo("Angel of the Ruins", 229, Rarity.UNCOMMON, mage.cards.a.AngelOfTheRuins.class)); + cards.add(new SetCardInfo("Anger", 289, Rarity.UNCOMMON, mage.cards.a.Anger.class)); + cards.add(new SetCardInfo("Arcane Sanctum", 373, Rarity.UNCOMMON, mage.cards.a.ArcaneSanctum.class)); + cards.add(new SetCardInfo("Arcane Signet", 332, Rarity.COMMON, mage.cards.a.ArcaneSignet.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Arcane Signet", 333, Rarity.COMMON, mage.cards.a.ArcaneSignet.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Arcane Signet", 334, Rarity.COMMON, mage.cards.a.ArcaneSignet.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Arcane Signet", 335, Rarity.COMMON, mage.cards.a.ArcaneSignet.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Archaeomancer's Map", 230, Rarity.RARE, mage.cards.a.ArchaeomancersMap.class)); + cards.add(new SetCardInfo("Archfiend of Depravity", 273, Rarity.RARE, mage.cards.a.ArchfiendOfDepravity.class)); + cards.add(new SetCardInfo("Archmage Emeritus", 261, Rarity.RARE, mage.cards.a.ArchmageEmeritus.class)); + cards.add(new SetCardInfo("Ardbert, Warrior of Darkness", 164, Rarity.RARE, mage.cards.a.ArdbertWarriorOfDarkness.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ardbert, Warrior of Darkness", 77, Rarity.RARE, mage.cards.a.ArdbertWarriorOfDarkness.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Armory Automaton", 336, Rarity.RARE, mage.cards.a.ArmoryAutomaton.class)); + cards.add(new SetCardInfo("Ash Barrens", 374, Rarity.COMMON, mage.cards.a.AshBarrens.class)); + cards.add(new SetCardInfo("Auron, Venerated Guardian", 10, Rarity.RARE, mage.cards.a.AuronVeneratedGuardian.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Auron, Venerated Guardian", 130, Rarity.RARE, mage.cards.a.AuronVeneratedGuardian.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Austere Command", 231, Rarity.RARE, mage.cards.a.AustereCommand.class)); + cards.add(new SetCardInfo("Authority of the Consuls", 232, Rarity.RARE, mage.cards.a.AuthorityOfTheConsuls.class)); + cards.add(new SetCardInfo("Avalanche of Sector 7", 150, Rarity.RARE, mage.cards.a.AvalancheOfSector7.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Avalanche of Sector 7", 53, Rarity.RARE, mage.cards.a.AvalancheOfSector7.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Baleful Strix", 318, Rarity.RARE, mage.cards.b.BalefulStrix.class)); + cards.add(new SetCardInfo("Bane of Progress", 299, Rarity.RARE, mage.cards.b.BaneOfProgress.class)); + cards.add(new SetCardInfo("Barret, Avalanche Leader", 166, Rarity.RARE, mage.cards.b.BarretAvalancheLeader.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Barret, Avalanche Leader", 79, Rarity.RARE, mage.cards.b.BarretAvalancheLeader.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bastion Protector", 233, Rarity.RARE, mage.cards.b.BastionProtector.class)); + cards.add(new SetCardInfo("Bastion of Remembrance", 274, Rarity.UNCOMMON, mage.cards.b.BastionOfRemembrance.class)); + cards.add(new SetCardInfo("Battlefield Forge", 375, Rarity.RARE, mage.cards.b.BattlefieldForge.class)); + cards.add(new SetCardInfo("Bedevil", 319, Rarity.RARE, mage.cards.b.Bedevil.class)); + cards.add(new SetCardInfo("Behemoth Sledge", 320, Rarity.UNCOMMON, mage.cards.b.BehemothSledge.class)); + cards.add(new SetCardInfo("Big Score", 290, Rarity.COMMON, mage.cards.b.BigScore.class)); + cards.add(new SetCardInfo("Blue Mage's Cane", 112, Rarity.RARE, mage.cards.b.BlueMagesCane.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Blue Mage's Cane", 35, Rarity.RARE, mage.cards.b.BlueMagesCane.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bonders' Enclave", 376, Rarity.RARE, mage.cards.b.BondersEnclave.class)); + cards.add(new SetCardInfo("Bred for the Hunt", 321, Rarity.UNCOMMON, mage.cards.b.BredForTheHunt.class)); + cards.add(new SetCardInfo("Bronze Guardian", 234, Rarity.RARE, mage.cards.b.BronzeGuardian.class)); + cards.add(new SetCardInfo("Brushland", 377, Rarity.RARE, mage.cards.b.Brushland.class)); + cards.add(new SetCardInfo("Bugenhagen, Wise Elder", 159, Rarity.RARE, mage.cards.b.BugenhagenWiseElder.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bugenhagen, Wise Elder", 66, Rarity.RARE, mage.cards.b.BugenhagenWiseElder.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cait Sith, Fortune Teller", 151, Rarity.RARE, mage.cards.c.CaitSithFortuneTeller.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cait Sith, Fortune Teller", 54, Rarity.RARE, mage.cards.c.CaitSithFortuneTeller.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Canopy Vista", 378, Rarity.RARE, mage.cards.c.CanopyVista.class)); cards.add(new SetCardInfo("Celes, Rune Knight", 1, Rarity.MYTHIC, mage.cards.c.CelesRuneKnight.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Celes, Rune Knight", 167, Rarity.MYTHIC, mage.cards.c.CelesRuneKnight.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Celes, Rune Knight", 201, Rarity.MYTHIC, mage.cards.c.CelesRuneKnight.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Celes, Rune Knight", 209, Rarity.MYTHIC, mage.cards.c.CelesRuneKnight.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Celes, Rune Knight", 220, Rarity.MYTHIC, mage.cards.c.CelesRuneKnight.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Cloud, Ex-SOLDIER", 2, Rarity.MYTHIC, mage.cards.c.CloudExSOLDIER.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Champion's Helm", 337, Rarity.RARE, mage.cards.c.ChampionsHelm.class)); + cards.add(new SetCardInfo("Champions from Beyond", 101, Rarity.RARE, mage.cards.c.ChampionsFromBeyond.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Champions from Beyond", 11, Rarity.RARE, mage.cards.c.ChampionsFromBeyond.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Chaos Warp", 291, Rarity.RARE, mage.cards.c.ChaosWarp.class)); + cards.add(new SetCardInfo("Chasm Skulker", 262, Rarity.RARE, mage.cards.c.ChasmSkulker.class)); + cards.add(new SetCardInfo("Chocobo Knights", 102, Rarity.RARE, mage.cards.c.ChocoboKnights.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Chocobo Knights", 12, Rarity.RARE, mage.cards.c.ChocoboKnights.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Choked Estuary", 379, Rarity.RARE, mage.cards.c.ChokedEstuary.class)); + cards.add(new SetCardInfo("Cid, Freeflier Pilot", 13, Rarity.RARE, mage.cards.c.CidFreeflierPilot.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cid, Freeflier Pilot", 131, Rarity.RARE, mage.cards.c.CidFreeflierPilot.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cinder Glade", 380, Rarity.RARE, mage.cards.c.CinderGlade.class)); + cards.add(new SetCardInfo("Cleansing Nova", 235, Rarity.RARE, mage.cards.c.CleansingNova.class)); + cards.add(new SetCardInfo("Clever Concealment", 236, Rarity.RARE, mage.cards.c.CleverConcealment.class)); + cards.add(new SetCardInfo("Clifftop Retreat", 381, Rarity.RARE, mage.cards.c.ClifftopRetreat.class)); + cards.add(new SetCardInfo("Cloud's Limit Break", 103, Rarity.RARE, mage.cards.c.CloudsLimitBreak.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cloud's Limit Break", 14, Rarity.RARE, mage.cards.c.CloudsLimitBreak.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Cloud, Ex-SOLDIER", 168, Rarity.MYTHIC, mage.cards.c.CloudExSOLDIER.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cloud, Ex-SOLDIER", 2, Rarity.MYTHIC, mage.cards.c.CloudExSOLDIER.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Cloud, Ex-SOLDIER", 202, Rarity.MYTHIC, mage.cards.c.CloudExSOLDIER.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cloud, Ex-SOLDIER", 210, Rarity.MYTHIC, mage.cards.c.CloudExSOLDIER.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Cloud, Ex-SOLDIER", 221, Rarity.MYTHIC, mage.cards.c.CloudExSOLDIER.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Terra, Herald of Hope", 4, Rarity.MYTHIC, mage.cards.t.TerraHeraldOfHope.class)); - cards.add(new SetCardInfo("Y'shtola, Night's Blessed", 7, Rarity.MYTHIC, mage.cards.y.YshtolaNightsBlessed.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Collective Effort", 237, Rarity.RARE, mage.cards.c.CollectiveEffort.class)); + cards.add(new SetCardInfo("Colossus Hammer", 338, Rarity.UNCOMMON, mage.cards.c.ColossusHammer.class)); + cards.add(new SetCardInfo("Combustible Gearhulk", 292, Rarity.MYTHIC, mage.cards.c.CombustibleGearhulk.class)); + cards.add(new SetCardInfo("Command Tower", 382, Rarity.COMMON, mage.cards.c.CommandTower.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Command Tower", 484, Rarity.COMMON, mage.cards.c.CommandTower.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Command Tower", 485, Rarity.COMMON, mage.cards.c.CommandTower.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Command Tower", 486, Rarity.COMMON, mage.cards.c.CommandTower.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Commander's Sphere", 339, Rarity.COMMON, mage.cards.c.CommandersSphere.class)); + cards.add(new SetCardInfo("Conqueror's Flail", 340, Rarity.RARE, mage.cards.c.ConquerorsFlail.class)); + cards.add(new SetCardInfo("Contaminated Aquifer", 383, Rarity.COMMON, mage.cards.c.ContaminatedAquifer.class)); + cards.add(new SetCardInfo("Coveted Jewel", 341, Rarity.RARE, mage.cards.c.CovetedJewel.class)); + cards.add(new SetCardInfo("Crackling Doom", 322, Rarity.UNCOMMON, mage.cards.c.CracklingDoom.class)); + cards.add(new SetCardInfo("Crux of Fate", 275, Rarity.RARE, mage.cards.c.CruxOfFate.class)); + cards.add(new SetCardInfo("Cultivate", 300, Rarity.COMMON, mage.cards.c.Cultivate.class)); + cards.add(new SetCardInfo("Cut a Deal", 238, Rarity.UNCOMMON, mage.cards.c.CutADeal.class)); + cards.add(new SetCardInfo("Cyan, Vengeful Samurai", 132, Rarity.RARE, mage.cards.c.CyanVengefulSamurai.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cyan, Vengeful Samurai", 16, Rarity.RARE, mage.cards.c.CyanVengefulSamurai.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Damning Verdict", 239, Rarity.RARE, mage.cards.d.DamningVerdict.class)); + cards.add(new SetCardInfo("Dancer's Chakrams", 17, Rarity.RARE, mage.cards.d.DancersChakrams.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Dancer's Chakrams", 105, Rarity.RARE, mage.cards.d.DancersChakrams.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Darksteel Plate", 342, Rarity.RARE, mage.cards.d.DarksteelPlate.class)); + cards.add(new SetCardInfo("Darkwater Catacombs", 384, Rarity.RARE, mage.cards.d.DarkwaterCatacombs.class)); + cards.add(new SetCardInfo("Decimate", 323, Rarity.RARE, mage.cards.d.Decimate.class)); + cards.add(new SetCardInfo("Demolition Field", 385, Rarity.UNCOMMON, mage.cards.d.DemolitionField.class)); + cards.add(new SetCardInfo("Desolate Mire", 386, Rarity.RARE, mage.cards.d.DesolateMire.class)); + cards.add(new SetCardInfo("Destroy Evil", 240, Rarity.COMMON, mage.cards.d.DestroyEvil.class)); + cards.add(new SetCardInfo("Dig Through Time", 263, Rarity.RARE, mage.cards.d.DigThroughTime.class)); + cards.add(new SetCardInfo("Dispatch", 241, Rarity.UNCOMMON, mage.cards.d.Dispatch.class)); + cards.add(new SetCardInfo("Dragonskull Summit", 387, Rarity.RARE, mage.cards.d.DragonskullSummit.class)); + cards.add(new SetCardInfo("Drowned Catacomb", 388, Rarity.RARE, mage.cards.d.DrownedCatacomb.class)); + cards.add(new SetCardInfo("Duskshell Crawler", 301, Rarity.COMMON, mage.cards.d.DuskshellCrawler.class)); + cards.add(new SetCardInfo("Elena, Turk Recruit", 133, Rarity.RARE, mage.cards.e.ElenaTurkRecruit.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Elena, Turk Recruit", 18, Rarity.RARE, mage.cards.e.ElenaTurkRecruit.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Endless Detour", 324, Rarity.RARE, mage.cards.e.EndlessDetour.class)); + cards.add(new SetCardInfo("Espers to Magicite", 114, Rarity.RARE, mage.cards.e.EspersToMagicite.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Espers to Magicite", 43, Rarity.RARE, mage.cards.e.EspersToMagicite.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Everflowing Chalice", 343, Rarity.UNCOMMON, mage.cards.e.EverflowingChalice.class)); + cards.add(new SetCardInfo("Evolving Wilds", 389, Rarity.COMMON, mage.cards.e.EvolvingWilds.class)); + cards.add(new SetCardInfo("Exotic Orchard", 390, Rarity.RARE, mage.cards.e.ExoticOrchard.class)); + cards.add(new SetCardInfo("Explorer's Scope", 344, Rarity.COMMON, mage.cards.e.ExplorersScope.class)); + cards.add(new SetCardInfo("Exsanguinate", 276, Rarity.UNCOMMON, mage.cards.e.Exsanguinate.class)); + cards.add(new SetCardInfo("Eye of Nidhogg", 115, Rarity.RARE, mage.cards.e.EyeOfNidhogg.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Eye of Nidhogg", 44, Rarity.RARE, mage.cards.e.EyeOfNidhogg.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Farewell", 242, Rarity.RARE, mage.cards.f.Farewell.class)); + cards.add(new SetCardInfo("Farseek", 302, Rarity.COMMON, mage.cards.f.Farseek.class)); + cards.add(new SetCardInfo("Fathom Mage", 325, Rarity.RARE, mage.cards.f.FathomMage.class)); + cards.add(new SetCardInfo("Fetid Heath", 391, Rarity.RARE, mage.cards.f.FetidHeath.class)); + cards.add(new SetCardInfo("Fight Rigging", 303, Rarity.RARE, mage.cards.f.FightRigging.class)); + cards.add(new SetCardInfo("Final Judgment", 243, Rarity.MYTHIC, mage.cards.f.FinalJudgment.class)); + cards.add(new SetCardInfo("Fire-Lit Thicket", 392, Rarity.RARE, mage.cards.f.FireLitThicket.class)); + cards.add(new SetCardInfo("Flayer of the Hatebound", 293, Rarity.RARE, mage.cards.f.FlayerOfTheHatebound.class)); + cards.add(new SetCardInfo("Flooded Grove", 393, Rarity.RARE, mage.cards.f.FloodedGrove.class)); + cards.add(new SetCardInfo("Foreboding Ruins", 394, Rarity.RARE, mage.cards.f.ForebodingRuins.class)); + cards.add(new SetCardInfo("Forge of Heroes", 395, Rarity.COMMON, mage.cards.f.ForgeOfHeroes.class)); + cards.add(new SetCardInfo("Forgotten Ancient", 304, Rarity.RARE, mage.cards.f.ForgottenAncient.class)); + cards.add(new SetCardInfo("Fortified Village", 396, Rarity.RARE, mage.cards.f.FortifiedVillage.class)); + cards.add(new SetCardInfo("Furious Rise", 294, Rarity.UNCOMMON, mage.cards.f.FuriousRise.class)); + cards.add(new SetCardInfo("Furycalm Snarl", 397, Rarity.RARE, mage.cards.f.FurycalmSnarl.class)); + cards.add(new SetCardInfo("Game Trail", 398, Rarity.RARE, mage.cards.g.GameTrail.class)); + cards.add(new SetCardInfo("Gau, Feral Youth", 152, Rarity.RARE, mage.cards.g.GauFeralYouth.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Gau, Feral Youth", 55, Rarity.RARE, mage.cards.g.GauFeralYouth.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("General Leo Cristophe", 135, Rarity.RARE, mage.cards.g.GeneralLeoCristophe.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("General Leo Cristophe", 20, Rarity.RARE, mage.cards.g.GeneralLeoCristophe.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Generous Patron", 305, Rarity.RARE, mage.cards.g.GenerousPatron.class)); + cards.add(new SetCardInfo("Geothermal Bog", 399, Rarity.COMMON, mage.cards.g.GeothermalBog.class)); + cards.add(new SetCardInfo("Glacial Fortress", 400, Rarity.RARE, mage.cards.g.GlacialFortress.class)); + cards.add(new SetCardInfo("Gogo, Mysterious Mime", 153, Rarity.RARE, mage.cards.g.GogoMysteriousMime.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Gogo, Mysterious Mime", 56, Rarity.RARE, mage.cards.g.GogoMysteriousMime.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Grateful Apparition", 244, Rarity.UNCOMMON, mage.cards.g.GratefulApparition.class)); + cards.add(new SetCardInfo("Graven Cairns", 401, Rarity.RARE, mage.cards.g.GravenCairns.class)); + cards.add(new SetCardInfo("Gyre Sage", 306, Rarity.RARE, mage.cards.g.GyreSage.class)); + cards.add(new SetCardInfo("Hardened Scales", 307, Rarity.RARE, mage.cards.h.HardenedScales.class)); + cards.add(new SetCardInfo("Harmonize", 308, Rarity.UNCOMMON, mage.cards.h.Harmonize.class)); + cards.add(new SetCardInfo("Heidegger, Shinra Executive", 136, Rarity.RARE, mage.cards.h.HeideggerShinraExecutive.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Heidegger, Shinra Executive", 21, Rarity.RARE, mage.cards.h.HeideggerShinraExecutive.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Helitrooper", 106, Rarity.RARE, mage.cards.h.Helitrooper.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Helitrooper", 22, Rarity.RARE, mage.cards.h.Helitrooper.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hellkite Tyrant", 295, Rarity.RARE, mage.cards.h.HellkiteTyrant.class)); + cards.add(new SetCardInfo("Herald's Horn", 228, Rarity.RARE, mage.cards.h.HeraldsHorn.class)); + cards.add(new SetCardInfo("Hermes, Overseer of Elpis", 141, Rarity.RARE, mage.cards.h.HermesOverseerOfElpis.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hermes, Overseer of Elpis", 36, Rarity.RARE, mage.cards.h.HermesOverseerOfElpis.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hero's Blade", 345, Rarity.UNCOMMON, mage.cards.h.HerosBlade.class)); + cards.add(new SetCardInfo("Hero's Heirloom", 346, Rarity.UNCOMMON, mage.cards.h.HerosHeirloom.class)); + cards.add(new SetCardInfo("High Market", 402, Rarity.RARE, mage.cards.h.HighMarket.class)); + cards.add(new SetCardInfo("Hildibrand Manderville", 173, Rarity.RARE, mage.cards.h.HildibrandManderville.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hildibrand Manderville", 83, Rarity.RARE, mage.cards.h.HildibrandManderville.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hinterland Harbor", 403, Rarity.RARE, mage.cards.h.HinterlandHarbor.class)); + cards.add(new SetCardInfo("Hraesvelgr of the First Brood", 142, Rarity.RARE, mage.cards.h.HraesvelgrOfTheFirstBrood.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hraesvelgr of the First Brood", 37, Rarity.RARE, mage.cards.h.HraesvelgrOfTheFirstBrood.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hypnotic Sprite", 264, Rarity.UNCOMMON, mage.cards.h.HypnoticSprite.class)); + cards.add(new SetCardInfo("Idyllic Beachfront", 404, Rarity.COMMON, mage.cards.i.IdyllicBeachfront.class)); + cards.add(new SetCardInfo("Incubation Druid", 309, Rarity.RARE, mage.cards.i.IncubationDruid.class)); + cards.add(new SetCardInfo("Inexorable Tide", 265, Rarity.RARE, mage.cards.i.InexorableTide.class)); + cards.add(new SetCardInfo("Inspiring Call", 310, Rarity.UNCOMMON, mage.cards.i.InspiringCall.class)); + cards.add(new SetCardInfo("Inspiring Statuary", 347, Rarity.RARE, mage.cards.i.InspiringStatuary.class)); + cards.add(new SetCardInfo("Interceptor, Shadow's Hound", 147, Rarity.RARE, mage.cards.i.InterceptorShadowsHound.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Interceptor, Shadow's Hound", 47, Rarity.RARE, mage.cards.i.InterceptorShadowsHound.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Into the Story", 266, Rarity.UNCOMMON, mage.cards.i.IntoTheStory.class)); + cards.add(new SetCardInfo("Isolated Chapel", 405, Rarity.RARE, mage.cards.i.IsolatedChapel.class)); + cards.add(new SetCardInfo("Jungle Shrine", 406, Rarity.UNCOMMON, mage.cards.j.JungleShrine.class)); + cards.add(new SetCardInfo("Key to the City", 348, Rarity.RARE, mage.cards.k.KeyToTheCity.class)); + cards.add(new SetCardInfo("Kimahri, Valiant Guardian", 175, Rarity.RARE, mage.cards.k.KimahriValiantGuardian.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Kimahri, Valiant Guardian", 85, Rarity.RARE, mage.cards.k.KimahriValiantGuardian.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Krile Baldesion", 176, Rarity.RARE, mage.cards.k.KrileBaldesion.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Krile Baldesion", 86, Rarity.RARE, mage.cards.k.KrileBaldesion.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Legions to Ashes", 326, Rarity.RARE, mage.cards.l.LegionsToAshes.class)); + cards.add(new SetCardInfo("Lethal Scheme", 277, Rarity.RARE, mage.cards.l.LethalScheme.class)); + cards.add(new SetCardInfo("Lightning Greaves", 349, Rarity.UNCOMMON, mage.cards.l.LightningGreaves.class)); + cards.add(new SetCardInfo("Lingering Souls", 245, Rarity.UNCOMMON, mage.cards.l.LingeringSouls.class)); + cards.add(new SetCardInfo("Lord Jyscal Guado", 137, Rarity.RARE, mage.cards.l.LordJyscalGuado.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lord Jyscal Guado", 23, Rarity.RARE, mage.cards.l.LordJyscalGuado.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lulu, Stern Guardian", 143, Rarity.RARE, mage.cards.l.LuluSternGuardian.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lulu, Stern Guardian", 38, Rarity.RARE, mage.cards.l.LuluSternGuardian.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Luminous Broodmoth", 246, Rarity.MYTHIC, mage.cards.l.LuminousBroodmoth.class)); + cards.add(new SetCardInfo("Mask of Memory", 350, Rarity.UNCOMMON, mage.cards.m.MaskOfMemory.class)); + cards.add(new SetCardInfo("Meteor Golem", 351, Rarity.UNCOMMON, mage.cards.m.MeteorGolem.class)); + cards.add(new SetCardInfo("Millikin", 352, Rarity.UNCOMMON, mage.cards.m.Millikin.class)); + cards.add(new SetCardInfo("Mind Stone", 353, Rarity.UNCOMMON, mage.cards.m.MindStone.class)); + cards.add(new SetCardInfo("Morbid Opportunist", 278, Rarity.UNCOMMON, mage.cards.m.MorbidOpportunist.class)); + cards.add(new SetCardInfo("Mortify", 327, Rarity.UNCOMMON, mage.cards.m.Mortify.class)); + cards.add(new SetCardInfo("Mossfire Valley", 407, Rarity.RARE, mage.cards.m.MossfireValley.class)); + cards.add(new SetCardInfo("Murderous Rider", 279, Rarity.RARE, mage.cards.m.MurderousRider.class)); + cards.add(new SetCardInfo("Nature's Lore", 311, Rarity.COMMON, mage.cards.n.NaturesLore.class)); + cards.add(new SetCardInfo("Nesting Grounds", 408, Rarity.UNCOMMON, mage.cards.n.NestingGrounds.class)); + cards.add(new SetCardInfo("Night's Whisper", 280, Rarity.COMMON, mage.cards.n.NightsWhisper.class)); + cards.add(new SetCardInfo("Nomad Outpost", 409, Rarity.UNCOMMON, mage.cards.n.NomadOutpost.class)); + cards.add(new SetCardInfo("O'aka, Traveling Merchant", 144, Rarity.RARE, mage.cards.o.OakaTravelingMerchant.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("O'aka, Traveling Merchant", 39, Rarity.RARE, mage.cards.o.OakaTravelingMerchant.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Observed Stasis", 113, Rarity.RARE, mage.cards.o.ObservedStasis.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Observed Stasis", 40, Rarity.RARE, mage.cards.o.ObservedStasis.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Overflowing Basin", 410, Rarity.RARE, mage.cards.o.OverflowingBasin.class)); + cards.add(new SetCardInfo("Palace Jailer", 247, Rarity.UNCOMMON, mage.cards.p.PalaceJailer.class)); + cards.add(new SetCardInfo("Path of Ancestry", 411, Rarity.COMMON, mage.cards.p.PathOfAncestry.class)); + cards.add(new SetCardInfo("Path of Discovery", 312, Rarity.RARE, mage.cards.p.PathOfDiscovery.class)); + cards.add(new SetCardInfo("Path to Exile", 248, Rarity.UNCOMMON, mage.cards.p.PathToExile.class)); + cards.add(new SetCardInfo("Pitiless Plunderer", 281, Rarity.UNCOMMON, mage.cards.p.PitilessPlunderer.class)); + cards.add(new SetCardInfo("Port Town", 412, Rarity.RARE, mage.cards.p.PortTown.class)); + cards.add(new SetCardInfo("Prairie Stream", 413, Rarity.RARE, mage.cards.p.PrairieStream.class)); + cards.add(new SetCardInfo("Priest of Fell Rites", 328, Rarity.RARE, mage.cards.p.PriestOfFellRites.class)); + cards.add(new SetCardInfo("Professional Face-Breaker", 296, Rarity.RARE, mage.cards.p.ProfessionalFaceBreaker.class)); + cards.add(new SetCardInfo("Promise of Loyalty", 249, Rarity.RARE, mage.cards.p.PromiseOfLoyalty.class)); + cards.add(new SetCardInfo("Propaganda", 268, Rarity.UNCOMMON, mage.cards.p.Propaganda.class)); + cards.add(new SetCardInfo("Protection Magic", 107, Rarity.RARE, mage.cards.p.ProtectionMagic.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Protection Magic", 24, Rarity.RARE, mage.cards.p.ProtectionMagic.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Pull from Tomorrow", 269, Rarity.RARE, mage.cards.p.PullFromTomorrow.class)); + cards.add(new SetCardInfo("Puresteel Paladin", 250, Rarity.RARE, mage.cards.p.PuresteelPaladin.class)); + cards.add(new SetCardInfo("Radiant Grove", 414, Rarity.COMMON, mage.cards.r.RadiantGrove.class)); + cards.add(new SetCardInfo("Rampant Growth", 313, Rarity.COMMON, mage.cards.r.RampantGrowth.class)); + cards.add(new SetCardInfo("Rampant Rejuvenator", 314, Rarity.RARE, mage.cards.r.RampantRejuvenator.class)); + cards.add(new SetCardInfo("Reanimate", 282, Rarity.RARE, mage.cards.r.Reanimate.class)); + cards.add(new SetCardInfo("Red XIII, Proud Warrior", 181, Rarity.RARE, mage.cards.r.RedXIIIProudWarrior.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Red XIII, Proud Warrior", 91, Rarity.RARE, mage.cards.r.RedXIIIProudWarrior.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Relic of Legends", 354, Rarity.UNCOMMON, mage.cards.r.RelicOfLegends.class)); + cards.add(new SetCardInfo("Resourceful Defense", 251, Rarity.RARE, mage.cards.r.ResourcefulDefense.class)); + cards.add(new SetCardInfo("Rise of the Dark Realms", 283, Rarity.MYTHIC, mage.cards.r.RiseOfTheDarkRealms.class)); + cards.add(new SetCardInfo("Rite of Replication", 270, Rarity.RARE, mage.cards.r.RiteOfReplication.class)); + cards.add(new SetCardInfo("Rogue's Passage", 415, Rarity.UNCOMMON, mage.cards.r.RoguesPassage.class)); + cards.add(new SetCardInfo("Rootbound Crag", 416, Rarity.RARE, mage.cards.r.RootboundCrag.class)); + cards.add(new SetCardInfo("Rugged Prairie", 417, Rarity.RARE, mage.cards.r.RuggedPrairie.class)); + cards.add(new SetCardInfo("Ruin Grinder", 297, Rarity.RARE, mage.cards.r.RuinGrinder.class)); + cards.add(new SetCardInfo("Ruinous Ultimatum", 329, Rarity.RARE, mage.cards.r.RuinousUltimatum.class)); + cards.add(new SetCardInfo("SOLDIER Military Program", 108, Rarity.RARE, mage.cards.s.SOLDIERMilitaryProgram.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("SOLDIER Military Program", 25, Rarity.RARE, mage.cards.s.SOLDIERMilitaryProgram.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sabin, Master Monk", 154, Rarity.RARE, mage.cards.s.SabinMasterMonk.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sabin, Master Monk", 57, Rarity.RARE, mage.cards.s.SabinMasterMonk.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sacred Peaks", 418, Rarity.COMMON, mage.cards.s.SacredPeaks.class)); + cards.add(new SetCardInfo("Scavenger Grounds", 419, Rarity.RARE, mage.cards.s.ScavengerGrounds.class)); + cards.add(new SetCardInfo("Scholar of New Horizons", 252, Rarity.RARE, mage.cards.s.ScholarOfNewHorizons.class)); + cards.add(new SetCardInfo("Seaside Citadel", 420, Rarity.UNCOMMON, mage.cards.s.SeasideCitadel.class)); + cards.add(new SetCardInfo("Secret Rendezvous", 217, Rarity.UNCOMMON, mage.cards.s.SecretRendezvous.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Secret Rendezvous", 218, Rarity.UNCOMMON, mage.cards.s.SecretRendezvous.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Secret Rendezvous", 219, Rarity.UNCOMMON, mage.cards.s.SecretRendezvous.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Secret Rendezvous", 253, Rarity.UNCOMMON, mage.cards.s.SecretRendezvous.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sephiroth, Fallen Hero", 182, Rarity.RARE, mage.cards.s.SephirothFallenHero.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sephiroth, Fallen Hero", 92, Rarity.RARE, mage.cards.s.SephirothFallenHero.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sepulchral Primordial", 284, Rarity.RARE, mage.cards.s.SepulchralPrimordial.class)); + cards.add(new SetCardInfo("Setzer, Wandering Gambler", 183, Rarity.RARE, mage.cards.s.SetzerWanderingGambler.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Setzer, Wandering Gambler", 93, Rarity.RARE, mage.cards.s.SetzerWanderingGambler.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Shadowblood Ridge", 421, Rarity.RARE, mage.cards.s.ShadowbloodRidge.class)); + cards.add(new SetCardInfo("Shelinda, Yevon Acolyte", 184, Rarity.RARE, mage.cards.s.ShelindaYevonAcolyte.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Shelinda, Yevon Acolyte", 94, Rarity.RARE, mage.cards.s.ShelindaYevonAcolyte.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Shineshadow Snarl", 422, Rarity.RARE, mage.cards.s.ShineshadowSnarl.class)); + cards.add(new SetCardInfo("Siegfried, Famed Swordsman", 149, Rarity.RARE, mage.cards.s.SiegfriedFamedSwordsman.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Siegfried, Famed Swordsman", 51, Rarity.RARE, mage.cards.s.SiegfriedFamedSwordsman.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Skullclamp", 355, Rarity.UNCOMMON, mage.cards.s.Skullclamp.class)); + cards.add(new SetCardInfo("Skycloud Expanse", 423, Rarity.RARE, mage.cards.s.SkycloudExpanse.class)); + cards.add(new SetCardInfo("Slayers' Stronghold", 424, Rarity.RARE, mage.cards.s.SlayersStronghold.class)); + cards.add(new SetCardInfo("Smoldering Marsh", 425, Rarity.RARE, mage.cards.s.SmolderingMarsh.class)); + cards.add(new SetCardInfo("Snuff Out", 285, Rarity.COMMON, mage.cards.s.SnuffOut.class)); + cards.add(new SetCardInfo("Sol Ring", 356, Rarity.UNCOMMON, mage.cards.s.SolRing.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sol Ring", 357, Rarity.UNCOMMON, mage.cards.s.SolRing.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sol Ring", 358, Rarity.UNCOMMON, mage.cards.s.SolRing.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sol Ring", 359, Rarity.UNCOMMON, mage.cards.s.SolRing.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Solemn Simulacrum", 360, Rarity.RARE, mage.cards.s.SolemnSimulacrum.class)); + cards.add(new SetCardInfo("Sphere Grid", 123, Rarity.RARE, mage.cards.s.SphereGrid.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sphere Grid", 70, Rarity.RARE, mage.cards.s.SphereGrid.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spire of Industry", 426, Rarity.RARE, mage.cards.s.SpireOfIndustry.class)); + cards.add(new SetCardInfo("Stitch Together", 286, Rarity.UNCOMMON, mage.cards.s.StitchTogether.class)); + cards.add(new SetCardInfo("Stitcher's Supplier", 287, Rarity.UNCOMMON, mage.cards.s.StitchersSupplier.class)); + cards.add(new SetCardInfo("Sublime Epiphany", 271, Rarity.RARE, mage.cards.s.SublimeEpiphany.class)); + cards.add(new SetCardInfo("Sulfurous Springs", 427, Rarity.RARE, mage.cards.s.SulfurousSprings.class)); + cards.add(new SetCardInfo("Summon: Good King Mog XII", 194, Rarity.RARE, mage.cards.s.SummonGoodKingMogXII.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Summon: Good King Mog XII", 26, Rarity.RARE, mage.cards.s.SummonGoodKingMogXII.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Summon: Ixion", 195, Rarity.RARE, mage.cards.s.SummonIxion.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Summon: Ixion", 27, Rarity.RARE, mage.cards.s.SummonIxion.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Summon: Kujata", 199, Rarity.RARE, mage.cards.s.SummonKujata.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Summon: Kujata", 61, Rarity.RARE, mage.cards.s.SummonKujata.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Summon: Magus Sisters", 200, Rarity.RARE, mage.cards.s.SummonMagusSisters.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Summon: Magus Sisters", 71, Rarity.RARE, mage.cards.s.SummonMagusSisters.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Summon: Yojimbo", 196, Rarity.RARE, mage.cards.s.SummonYojimbo.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Summon: Yojimbo", 28, Rarity.RARE, mage.cards.s.SummonYojimbo.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Summoner's Sending", 109, Rarity.RARE, mage.cards.s.SummonersSending.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Summoner's Sending", 29, Rarity.RARE, mage.cards.s.SummonersSending.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Summoning Materia", 124, Rarity.RARE, mage.cards.s.SummoningMateria.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Summoning Materia", 72, Rarity.RARE, mage.cards.s.SummoningMateria.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sun Titan", 254, Rarity.MYTHIC, mage.cards.s.SunTitan.class)); + cards.add(new SetCardInfo("Sungrass Prairie", 428, Rarity.RARE, mage.cards.s.SungrassPrairie.class)); + cards.add(new SetCardInfo("Sunken Hollow", 429, Rarity.RARE, mage.cards.s.SunkenHollow.class)); + cards.add(new SetCardInfo("Sunken Ruins", 430, Rarity.RARE, mage.cards.s.SunkenRuins.class)); + cards.add(new SetCardInfo("Sunlit Marsh", 431, Rarity.COMMON, mage.cards.s.SunlitMarsh.class)); + cards.add(new SetCardInfo("Sunpetal Grove", 432, Rarity.RARE, mage.cards.s.SunpetalGrove.class)); + cards.add(new SetCardInfo("Sunscorch Regent", 255, Rarity.RARE, mage.cards.s.SunscorchRegent.class)); + cards.add(new SetCardInfo("Sunscorched Divide", 433, Rarity.RARE, mage.cards.s.SunscorchedDivide.class)); + cards.add(new SetCardInfo("Swiftfoot Boots", 361, Rarity.UNCOMMON, mage.cards.s.SwiftfootBoots.class)); + cards.add(new SetCardInfo("Sword of the Animist", 362, Rarity.RARE, mage.cards.s.SwordOfTheAnimist.class)); + cards.add(new SetCardInfo("Swords to Plowshares", 256, Rarity.UNCOMMON, mage.cards.s.SwordsToPlowshares.class)); + cards.add(new SetCardInfo("Syphon Mind", 288, Rarity.COMMON, mage.cards.s.SyphonMind.class)); + cards.add(new SetCardInfo("Talisman of Conviction", 363, Rarity.UNCOMMON, mage.cards.t.TalismanOfConviction.class)); + cards.add(new SetCardInfo("Talisman of Dominance", 364, Rarity.UNCOMMON, mage.cards.t.TalismanOfDominance.class)); + cards.add(new SetCardInfo("Talisman of Hierarchy", 365, Rarity.UNCOMMON, mage.cards.t.TalismanOfHierarchy.class)); + cards.add(new SetCardInfo("Talisman of Indulgence", 366, Rarity.UNCOMMON, mage.cards.t.TalismanOfIndulgence.class)); + cards.add(new SetCardInfo("Talisman of Progress", 367, Rarity.UNCOMMON, mage.cards.t.TalismanOfProgress.class)); + cards.add(new SetCardInfo("Tangled Islet", 434, Rarity.COMMON, mage.cards.t.TangledIslet.class)); + cards.add(new SetCardInfo("Tataru Taru", 138, Rarity.RARE, mage.cards.t.TataruTaru.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tataru Taru", 30, Rarity.RARE, mage.cards.t.TataruTaru.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Temple of Enlightenment", 435, Rarity.RARE, mage.cards.t.TempleOfEnlightenment.class)); + cards.add(new SetCardInfo("Temple of Mystery", 436, Rarity.RARE, mage.cards.t.TempleOfMystery.class)); + cards.add(new SetCardInfo("Temple of Plenty", 437, Rarity.RARE, mage.cards.t.TempleOfPlenty.class)); + cards.add(new SetCardInfo("Temple of the False God", 438, Rarity.UNCOMMON, mage.cards.t.TempleOfTheFalseGod.class)); + cards.add(new SetCardInfo("Terra, Herald of Hope", 186, Rarity.MYTHIC, mage.cards.t.TerraHeraldOfHope.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Terra, Herald of Hope", 204, Rarity.MYTHIC, mage.cards.t.TerraHeraldOfHope.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Terra, Herald of Hope", 212, Rarity.MYTHIC, mage.cards.t.TerraHeraldOfHope.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Terra, Herald of Hope", 223, Rarity.MYTHIC, mage.cards.t.TerraHeraldOfHope.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Terra, Herald of Hope", 4, Rarity.MYTHIC, mage.cards.t.TerraHeraldOfHope.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Thancred Waters", 139, Rarity.RARE, mage.cards.t.ThancredWaters.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Thancred Waters", 31, Rarity.RARE, mage.cards.t.ThancredWaters.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Falcon, Airship Restored", 116, Rarity.RARE, mage.cards.t.TheFalconAirshipRestored.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Falcon, Airship Restored", 45, Rarity.RARE, mage.cards.t.TheFalconAirshipRestored.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Warring Triad", 193, Rarity.RARE, mage.cards.t.TheWarringTriad.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Warring Triad", 99, Rarity.RARE, mage.cards.t.TheWarringTriad.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Thought Vessel", 368, Rarity.COMMON, mage.cards.t.ThoughtVessel.class)); + cards.add(new SetCardInfo("Three Visits", 315, Rarity.UNCOMMON, mage.cards.t.ThreeVisits.class)); + cards.add(new SetCardInfo("Tifa, Martial Artist", 188, Rarity.MYTHIC, mage.cards.t.TifaMartialArtist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tifa, Martial Artist", 206, Rarity.MYTHIC, mage.cards.t.TifaMartialArtist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tifa, Martial Artist", 214, Rarity.MYTHIC, mage.cards.t.TifaMartialArtist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tifa, Martial Artist", 225, Rarity.MYTHIC, mage.cards.t.TifaMartialArtist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tifa, Martial Artist", 6, Rarity.MYTHIC, mage.cards.t.TifaMartialArtist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tireless Tracker", 316, Rarity.RARE, mage.cards.t.TirelessTracker.class)); + cards.add(new SetCardInfo("Together Forever", 257, Rarity.RARE, mage.cards.t.TogetherForever.class)); + cards.add(new SetCardInfo("Tome of Legends", 369, Rarity.RARE, mage.cards.t.TomeOfLegends.class)); + cards.add(new SetCardInfo("Torrential Gearhulk", 272, Rarity.RARE, mage.cards.t.TorrentialGearhulk.class)); + cards.add(new SetCardInfo("Tragic Arrogance", 258, Rarity.RARE, mage.cards.t.TragicArrogance.class)); + cards.add(new SetCardInfo("Trailblazer's Boots", 370, Rarity.UNCOMMON, mage.cards.t.TrailblazersBoots.class)); + cards.add(new SetCardInfo("Transpose", 119, Rarity.RARE, mage.cards.t.Transpose.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Transpose", 52, Rarity.RARE, mage.cards.t.Transpose.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tromell, Seymour's Butler", 162, Rarity.RARE, mage.cards.t.TromellSeymoursButler.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tromell, Seymour's Butler", 73, Rarity.RARE, mage.cards.t.TromellSeymoursButler.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ultimate Magic: Holy", 110, Rarity.RARE, mage.cards.u.UltimateMagicHoly.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ultimate Magic: Holy", 32, Rarity.RARE, mage.cards.u.UltimateMagicHoly.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Umaro, Raging Yeti", 156, Rarity.RARE, mage.cards.u.UmaroRagingYeti.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Umaro, Raging Yeti", 63, Rarity.RARE, mage.cards.u.UmaroRagingYeti.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Underground River", 439, Rarity.RARE, mage.cards.u.UndergroundRiver.class)); + cards.add(new SetCardInfo("Unfinished Business", 259, Rarity.RARE, mage.cards.u.UnfinishedBusiness.class)); + cards.add(new SetCardInfo("Vandalblast", 298, Rarity.UNCOMMON, mage.cards.v.Vandalblast.class)); + cards.add(new SetCardInfo("Vanquish the Horde", 260, Rarity.RARE, mage.cards.v.VanquishTheHorde.class)); + cards.add(new SetCardInfo("Vincent, Vengeful Atoner", 157, Rarity.RARE, mage.cards.v.VincentVengefulAtoner.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vincent, Vengeful Atoner", 64, Rarity.RARE, mage.cards.v.VincentVengefulAtoner.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vindicate", 330, Rarity.RARE, mage.cards.v.Vindicate.class)); + cards.add(new SetCardInfo("Vineglimmer Snarl", 440, Rarity.RARE, mage.cards.v.VineglimmerSnarl.class)); + cards.add(new SetCardInfo("Void Rend", 331, Rarity.RARE, mage.cards.v.VoidRend.class)); + cards.add(new SetCardInfo("Walking Ballista", 371, Rarity.RARE, mage.cards.w.WalkingBallista.class)); + cards.add(new SetCardInfo("Wayfarer's Bauble", 372, Rarity.COMMON, mage.cards.w.WayfarersBauble.class)); + cards.add(new SetCardInfo("Wooded Ridgeline", 441, Rarity.COMMON, mage.cards.w.WoodedRidgeline.class)); + cards.add(new SetCardInfo("Wrecking Ball Arm", 100, Rarity.RARE, mage.cards.w.WreckingBallArm.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Wrecking Ball Arm", 128, Rarity.RARE, mage.cards.w.WreckingBallArm.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Y'shtola, Night's Blessed", 191, Rarity.MYTHIC, mage.cards.y.YshtolaNightsBlessed.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Y'shtola, Night's Blessed", 207, Rarity.MYTHIC, mage.cards.y.YshtolaNightsBlessed.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Y'shtola, Night's Blessed", 215, Rarity.MYTHIC, mage.cards.y.YshtolaNightsBlessed.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Y'shtola, Night's Blessed", 226, Rarity.MYTHIC, mage.cards.y.YshtolaNightsBlessed.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Y'shtola, Night's Blessed", 7, Rarity.MYTHIC, mage.cards.y.YshtolaNightsBlessed.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Yuffie, Materia Hunter", 158, Rarity.RARE, mage.cards.y.YuffieMateriaHunter.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Yuffie, Materia Hunter", 65, Rarity.RARE, mage.cards.y.YuffieMateriaHunter.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Yuna's Decision", 125, Rarity.RARE, mage.cards.y.YunasDecision.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Yuna's Decision", 74, Rarity.RARE, mage.cards.y.YunasDecision.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Yuna's Whistle", 126, Rarity.RARE, mage.cards.y.YunasWhistle.class, NON_FULL_USE_VARIOUS)); } } diff --git a/Mage.Sets/src/mage/sets/ModernHorizons3Commander.java b/Mage.Sets/src/mage/sets/ModernHorizons3Commander.java index 58cd18f64ab..75b160e0e42 100644 --- a/Mage.Sets/src/mage/sets/ModernHorizons3Commander.java +++ b/Mage.Sets/src/mage/sets/ModernHorizons3Commander.java @@ -35,8 +35,8 @@ public final class ModernHorizons3Commander extends ExpansionSet { cards.add(new SetCardInfo("All Is Dust", 152, Rarity.MYTHIC, mage.cards.a.AllIsDust.class)); cards.add(new SetCardInfo("Altar of the Goyf", 282, Rarity.UNCOMMON, mage.cards.a.AltarOfTheGoyf.class)); cards.add(new SetCardInfo("Ancient Stirrings", 219, Rarity.COMMON, mage.cards.a.AncientStirrings.class)); - cards.add(new SetCardInfo("Angelic Aberration", 39, Rarity.RARE, mage.cards.a.AngelicAberration.class)); cards.add(new SetCardInfo("Angel of Invention", 166, Rarity.MYTHIC, mage.cards.a.AngelOfInvention.class)); + cards.add(new SetCardInfo("Angelic Aberration", 39, Rarity.RARE, mage.cards.a.AngelicAberration.class)); cards.add(new SetCardInfo("Anger", 208, Rarity.UNCOMMON, mage.cards.a.Anger.class)); cards.add(new SetCardInfo("Apex Devastator", 220, Rarity.MYTHIC, mage.cards.a.ApexDevastator.class)); cards.add(new SetCardInfo("Arcane Denial", 176, Rarity.COMMON, mage.cards.a.ArcaneDenial.class)); @@ -121,6 +121,7 @@ public final class ModernHorizons3Commander extends ExpansionSet { cards.add(new SetCardInfo("Evolving Wilds", 340, Rarity.COMMON, mage.cards.e.EvolvingWilds.class)); cards.add(new SetCardInfo("Exotic Orchard", 341, Rarity.RARE, mage.cards.e.ExoticOrchard.class)); cards.add(new SetCardInfo("Expedition Map", 292, Rarity.COMMON, mage.cards.e.ExpeditionMap.class)); + cards.add(new SetCardInfo("Exterminator Magmarch", 72, Rarity.RARE, mage.cards.e.ExterminatorMagmarch.class)); cards.add(new SetCardInfo("Faithless Looting", 211, Rarity.COMMON, mage.cards.f.FaithlessLooting.class)); cards.add(new SetCardInfo("Farewell", 170, Rarity.RARE, mage.cards.f.Farewell.class)); cards.add(new SetCardInfo("Filigree Racer", 56, Rarity.RARE, mage.cards.f.FiligreeRacer.class)); @@ -138,8 +139,8 @@ public final class ModernHorizons3Commander extends ExpansionSet { cards.add(new SetCardInfo("Garruk, Apex Predator", 262, Rarity.MYTHIC, mage.cards.g.GarrukApexPredator.class)); cards.add(new SetCardInfo("Glimmer of Genius", 187, Rarity.UNCOMMON, mage.cards.g.GlimmerOfGenius.class)); cards.add(new SetCardInfo("Glimmerpost", 346, Rarity.COMMON, mage.cards.g.Glimmerpost.class)); - cards.add(new SetCardInfo("Goldspan Dragon", 212, Rarity.MYTHIC, mage.cards.g.GoldspanDragon.class)); cards.add(new SetCardInfo("Gluttonous Hellkite", 73, Rarity.RARE, mage.cards.g.GluttonousHellkite.class)); + cards.add(new SetCardInfo("Goldspan Dragon", 212, Rarity.MYTHIC, mage.cards.g.GoldspanDragon.class)); cards.add(new SetCardInfo("Gonti's Aether Heart", 294, Rarity.MYTHIC, mage.cards.g.GontisAetherHeart.class)); cards.add(new SetCardInfo("Grapple with the Past", 230, Rarity.COMMON, mage.cards.g.GrappleWithThePast.class)); cards.add(new SetCardInfo("Graveshifter", 198, Rarity.UNCOMMON, mage.cards.g.Graveshifter.class)); @@ -204,6 +205,7 @@ public final class ModernHorizons3Commander extends ExpansionSet { cards.add(new SetCardInfo("Oblivion Stone", 303, Rarity.RARE, mage.cards.o.OblivionStone.class)); cards.add(new SetCardInfo("Omo, Queen of Vesuva", 2, Rarity.MYTHIC, mage.cards.o.OmoQueenOfVesuva.class)); cards.add(new SetCardInfo("Opal Palace", 361, Rarity.COMMON, mage.cards.o.OpalPalace.class)); + cards.add(new SetCardInfo("Overclocked Electromancer", 58, Rarity.RARE, mage.cards.o.OverclockedElectromancer.class)); cards.add(new SetCardInfo("Overflowing Basin", 362, Rarity.RARE, mage.cards.o.OverflowingBasin.class)); cards.add(new SetCardInfo("Path of Ancestry", 363, Rarity.COMMON, mage.cards.p.PathOfAncestry.class)); cards.add(new SetCardInfo("Planar Nexus", 80, Rarity.RARE, mage.cards.p.PlanarNexus.class)); @@ -218,6 +220,7 @@ public final class ModernHorizons3Commander extends ExpansionSet { cards.add(new SetCardInfo("Quandrix Campus", 366, Rarity.COMMON, mage.cards.q.QuandrixCampus.class)); cards.add(new SetCardInfo("Raging Ravine", 367, Rarity.RARE, mage.cards.r.RagingRavine.class)); cards.add(new SetCardInfo("Rampaging Baloths", 239, Rarity.MYTHIC, mage.cards.r.RampagingBaloths.class)); + cards.add(new SetCardInfo("Rampant Frogantua", 66, Rarity.RARE, mage.cards.r.RampantFrogantua.class)); cards.add(new SetCardInfo("Rampant Growth", 240, Rarity.COMMON, mage.cards.r.RampantGrowth.class)); cards.add(new SetCardInfo("Ramunap Excavator", 241, Rarity.RARE, mage.cards.r.RamunapExcavator.class)); cards.add(new SetCardInfo("Razorfield Ripper", 42, Rarity.RARE, mage.cards.r.RazorfieldRipper.class)); @@ -245,7 +248,6 @@ public final class ModernHorizons3Commander extends ExpansionSet { cards.add(new SetCardInfo("Shivan Reef", 375, Rarity.RARE, mage.cards.s.ShivanReef.class)); cards.add(new SetCardInfo("Shrine of the Forsaken Gods", 376, Rarity.RARE, mage.cards.s.ShrineOfTheForsakenGods.class)); cards.add(new SetCardInfo("Siege-Gang Lieutenant", 61, Rarity.RARE, mage.cards.s.SiegeGangLieutenant.class)); - cards.add(new SetCardInfo("Ulalek, Fused Atrocity", 4, Rarity.MYTHIC, mage.cards.u.UlalekFusedAtrocity.class)); cards.add(new SetCardInfo("Sifter of Skulls", 203, Rarity.RARE, mage.cards.s.SifterOfSkulls.class)); cards.add(new SetCardInfo("Silverquill Lecturer", 44, Rarity.RARE, mage.cards.s.SilverquillLecturer.class)); cards.add(new SetCardInfo("Simic Growth Chamber", 377, Rarity.COMMON, mage.cards.s.SimicGrowthChamber.class)); @@ -291,6 +293,7 @@ public final class ModernHorizons3Commander extends ExpansionSet { cards.add(new SetCardInfo("Temple of Mystery", 390, Rarity.RARE, mage.cards.t.TempleOfMystery.class)); cards.add(new SetCardInfo("Temple of Silence", 391, Rarity.RARE, mage.cards.t.TempleOfSilence.class)); cards.add(new SetCardInfo("Temple of Triumph", 392, Rarity.RARE, mage.cards.t.TempleOfTriumph.class)); + cards.add(new SetCardInfo("Tempt with Mayhem", 62, Rarity.RARE, mage.cards.t.TemptWithMayhem.class)); cards.add(new SetCardInfo("Tendo Ice Bridge", 393, Rarity.RARE, mage.cards.t.TendoIceBridge.class)); cards.add(new SetCardInfo("Terastodon", 249, Rarity.RARE, mage.cards.t.Terastodon.class)); cards.add(new SetCardInfo("Terminate", 274, Rarity.COMMON, mage.cards.t.Terminate.class)); @@ -306,6 +309,7 @@ public final class ModernHorizons3Commander extends ExpansionSet { cards.add(new SetCardInfo("Twins of Discord", 37, Rarity.RARE, mage.cards.t.TwinsOfDiscord.class)); cards.add(new SetCardInfo("Ugin's Insight", 196, Rarity.RARE, mage.cards.u.UginsInsight.class)); cards.add(new SetCardInfo("Ugin, the Ineffable", 160, Rarity.RARE, mage.cards.u.UginTheIneffable.class)); + cards.add(new SetCardInfo("Ulalek, Fused Atrocity", 4, Rarity.MYTHIC, mage.cards.u.UlalekFusedAtrocity.class)); cards.add(new SetCardInfo("Ulamog's Crusher", 161, Rarity.COMMON, mage.cards.u.UlamogsCrusher.class)); cards.add(new SetCardInfo("Ulamog's Dreadsire", 38, Rarity.RARE, mage.cards.u.UlamogsDreadsire.class)); cards.add(new SetCardInfo("Ulamog's Nullifier", 275, Rarity.UNCOMMON, mage.cards.u.UlamogsNullifier.class)); diff --git a/Mage.Sets/src/mage/sets/MurdersAtKarlovManorCommander.java b/Mage.Sets/src/mage/sets/MurdersAtKarlovManorCommander.java index 067dd103e5c..b397fe215df 100644 --- a/Mage.Sets/src/mage/sets/MurdersAtKarlovManorCommander.java +++ b/Mage.Sets/src/mage/sets/MurdersAtKarlovManorCommander.java @@ -177,6 +177,7 @@ public final class MurdersAtKarlovManorCommander extends ExpansionSet { cards.add(new SetCardInfo("Mirko, Obsessive Theorist", 316, Rarity.MYTHIC, mage.cards.m.MirkoObsessiveTheorist.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mirror Entity", 75, Rarity.RARE, mage.cards.m.MirrorEntity.class)); cards.add(new SetCardInfo("Mission Briefing", 110, Rarity.RARE, mage.cards.m.MissionBriefing.class)); + cards.add(new SetCardInfo("Mob Verdict", 33, Rarity.RARE, mage.cards.m.MobVerdict.class)); cards.add(new SetCardInfo("Morska, Undersea Sleuth", 3, Rarity.MYTHIC, mage.cards.m.MorskaUnderseaSleuth.class)); cards.add(new SetCardInfo("Mossfire Valley", 274, Rarity.RARE, mage.cards.m.MossfireValley.class)); cards.add(new SetCardInfo("Mosswort Bridge", 275, Rarity.RARE, mage.cards.m.MosswortBridge.class)); diff --git a/Mage.Sets/src/mage/sets/RavnicaClueEdition.java b/Mage.Sets/src/mage/sets/RavnicaClueEdition.java index 80cc33a105e..9b71bfb42a0 100644 --- a/Mage.Sets/src/mage/sets/RavnicaClueEdition.java +++ b/Mage.Sets/src/mage/sets/RavnicaClueEdition.java @@ -103,7 +103,7 @@ public final class RavnicaClueEdition extends ExpansionSet { cards.add(new SetCardInfo("Forest", 272, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Forest", 273, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Frenzied Goblin", 135, Rarity.UNCOMMON, mage.cards.f.FrenziedGoblin.class)); - //cards.add(new SetCardInfo("Frenzied Gorespawn", 32, Rarity.RARE, mage.cards.f.FrenziedGorespawn.class)); + cards.add(new SetCardInfo("Frenzied Gorespawn", 32, Rarity.RARE, mage.cards.f.FrenziedGorespawn.class)); cards.add(new SetCardInfo("Fresh-Faced Recruit", 192, Rarity.COMMON, mage.cards.f.FreshFacedRecruit.class)); cards.add(new SetCardInfo("Frostburn Weird", 193, Rarity.COMMON, mage.cards.f.FrostburnWeird.class)); cards.add(new SetCardInfo("Fungal Rebirth", 163, Rarity.UNCOMMON, mage.cards.f.FungalRebirth.class)); @@ -129,7 +129,7 @@ public final class RavnicaClueEdition extends ExpansionSet { cards.add(new SetCardInfo("Havoc Jester", 138, Rarity.UNCOMMON, mage.cards.h.HavocJester.class)); cards.add(new SetCardInfo("Headliner Scarlett", 4, Rarity.RARE, mage.cards.h.HeadlinerScarlett.class)); cards.add(new SetCardInfo("Helium Squirter", 87, Rarity.COMMON, mage.cards.h.HeliumSquirter.class)); - //cards.add(new SetCardInfo("Herald of Ilharg", 34, Rarity.RARE, mage.cards.h.HeraldOfIlharg.class)); + cards.add(new SetCardInfo("Herald of Ilharg", 34, Rarity.RARE, mage.cards.h.HeraldOfIlharg.class)); cards.add(new SetCardInfo("Hydroid Krasis", 195, Rarity.MYTHIC, mage.cards.h.HydroidKrasis.class)); cards.add(new SetCardInfo("Hypersonic Dragon", 196, Rarity.RARE, mage.cards.h.HypersonicDragon.class)); cards.add(new SetCardInfo("Incriminating Impetus", 35, Rarity.UNCOMMON, mage.cards.i.IncriminatingImpetus.class)); diff --git a/Mage.Sets/src/mage/sets/SecretLairDrop.java b/Mage.Sets/src/mage/sets/SecretLairDrop.java index 75052ff1506..c6eb9f9e797 100644 --- a/Mage.Sets/src/mage/sets/SecretLairDrop.java +++ b/Mage.Sets/src/mage/sets/SecretLairDrop.java @@ -810,7 +810,7 @@ public class SecretLairDrop extends ExpansionSet { cards.add(new SetCardInfo("Elesh Norn, Grand Cenobite", 811, Rarity.MYTHIC, mage.cards.e.EleshNornGrandCenobite.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Seven Dwarves", 813, Rarity.RARE, mage.cards.s.SevenDwarves.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Seven Dwarves", 814, Rarity.RARE, mage.cards.s.SevenDwarves.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Seven Dwarves", 815, Rarity.COMMON, mage.cards.s.SevenDwarves.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Seven Dwarves", 815, Rarity.RARE, mage.cards.s.SevenDwarves.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Arcane Signet", 820, Rarity.RARE, mage.cards.a.ArcaneSignet.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Arcane Signet", "820*", Rarity.RARE, mage.cards.a.ArcaneSignet.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Echo of Eons", 821, Rarity.RARE, mage.cards.e.EchoOfEons.class, RETRO_ART)); @@ -1517,7 +1517,7 @@ public class SecretLairDrop extends ExpansionSet { cards.add(new SetCardInfo("Admiral Beckett Brass", 1561, Rarity.MYTHIC, mage.cards.a.AdmiralBeckettBrass.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Skithiryx, the Blight Dragon", 1562, Rarity.MYTHIC, mage.cards.s.SkithiryxTheBlightDragon.class)); cards.add(new SetCardInfo("Kaalia of the Vast", 1563, Rarity.MYTHIC, mage.cards.k.KaaliaOfTheVast.class)); - cards.add(new SetCardInfo("Angel of Despair", 1564, Rarity.UNCOMMON, mage.cards.a.AngelOfDespair.class)); + cards.add(new SetCardInfo("Angel of Despair", 1564, Rarity.RARE, mage.cards.a.AngelOfDespair.class)); cards.add(new SetCardInfo("Master of Cruelties", 1565, Rarity.MYTHIC, mage.cards.m.MasterOfCruelties.class)); cards.add(new SetCardInfo("Gonti, Lord of Luxury", 1566, Rarity.RARE, mage.cards.g.GontiLordOfLuxury.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Gonti, Lord of Luxury", "1566*", Rarity.RARE, mage.cards.g.GontiLordOfLuxury.class, NON_FULL_USE_VARIOUS)); diff --git a/Mage.Sets/src/mage/sets/Warhammer40000Commander.java b/Mage.Sets/src/mage/sets/Warhammer40000Commander.java index 5b326c80179..c009b193fdd 100644 --- a/Mage.Sets/src/mage/sets/Warhammer40000Commander.java +++ b/Mage.Sets/src/mage/sets/Warhammer40000Commander.java @@ -20,289 +20,623 @@ public final class Warhammer40000Commander extends ExpansionSet { this.hasBasicLands = true; this.hasBoosters = false; - cards.add(new SetCardInfo("Abaddon the Despoiler", 2, Rarity.MYTHIC, mage.cards.a.AbaddonTheDespoiler.class)); - cards.add(new SetCardInfo("Aberrant", 86, Rarity.UNCOMMON, mage.cards.a.Aberrant.class)); - cards.add(new SetCardInfo("Abundance", 210, Rarity.RARE, mage.cards.a.Abundance.class)); - cards.add(new SetCardInfo("Acolyte Hybrid", 70, Rarity.UNCOMMON, mage.cards.a.AcolyteHybrid.class)); - cards.add(new SetCardInfo("Aetherize", 191, Rarity.UNCOMMON, mage.cards.a.Aetherize.class)); - cards.add(new SetCardInfo("And They Shall Know No Fear", 9, Rarity.UNCOMMON, mage.cards.a.AndTheyShallKnowNoFear.class)); - cards.add(new SetCardInfo("Anrakyr the Traveller", 28, Rarity.RARE, mage.cards.a.AnrakyrTheTraveller.class)); - cards.add(new SetCardInfo("Arcane Sanctum", 264, Rarity.UNCOMMON, mage.cards.a.ArcaneSanctum.class)); - cards.add(new SetCardInfo("Arcane Signet", 227, Rarity.COMMON, mage.cards.a.ArcaneSignet.class)); - cards.add(new SetCardInfo("Arco-Flagellant", 29, Rarity.RARE, mage.cards.a.ArcoFlagellant.class)); - cards.add(new SetCardInfo("Ash Barrens", 265, Rarity.UNCOMMON, mage.cards.a.AshBarrens.class)); - cards.add(new SetCardInfo("Aspiring Champion", 71, Rarity.RARE, mage.cards.a.AspiringChampion.class)); - cards.add(new SetCardInfo("Assault Intercessor", 104, Rarity.RARE, mage.cards.a.AssaultIntercessor.class)); - cards.add(new SetCardInfo("Assault Suit", 230, Rarity.UNCOMMON, mage.cards.a.AssaultSuit.class)); - cards.add(new SetCardInfo("Atalan Jackal", 105, Rarity.RARE, mage.cards.a.AtalanJackal.class)); - cards.add(new SetCardInfo("Barren Moor", 266, Rarity.UNCOMMON, mage.cards.b.BarrenMoor.class)); - cards.add(new SetCardInfo("Bastion Protector", 182, Rarity.RARE, mage.cards.b.BastionProtector.class)); - cards.add(new SetCardInfo("Be'lakor, the Dark Master", 6, Rarity.MYTHIC, mage.cards.b.BelakorTheDarkMaster.class)); - cards.add(new SetCardInfo("Beacon of Unrest", 194, Rarity.RARE, mage.cards.b.BeaconOfUnrest.class)); - cards.add(new SetCardInfo("Belisarius Cawl", 106, Rarity.RARE, mage.cards.b.BelisariusCawl.class)); - cards.add(new SetCardInfo("Bile Blight", 195, Rarity.UNCOMMON, mage.cards.b.BileBlight.class)); - cards.add(new SetCardInfo("Biophagus", 87, Rarity.RARE, mage.cards.b.Biophagus.class)); - cards.add(new SetCardInfo("Biotransference", 30, Rarity.RARE, mage.cards.b.Biotransference.class)); - cards.add(new SetCardInfo("Birth of the Imperium", 107, Rarity.RARE, mage.cards.b.BirthOfTheImperium.class)); - cards.add(new SetCardInfo("Bituminous Blast", 221, Rarity.UNCOMMON, mage.cards.b.BituminousBlast.class)); - cards.add(new SetCardInfo("Blasphemous Act", 204, Rarity.RARE, mage.cards.b.BlasphemousAct.class)); - cards.add(new SetCardInfo("Blight Grenade", 31, Rarity.RARE, mage.cards.b.BlightGrenade.class)); - cards.add(new SetCardInfo("Blood for the Blood God!", 108, Rarity.RARE, mage.cards.b.BloodForTheBloodGod.class)); - cards.add(new SetCardInfo("Bloodcrusher of Khorne", 72, Rarity.UNCOMMON, mage.cards.b.BloodcrusherOfKhorne.class)); - cards.add(new SetCardInfo("Bloodthirster", 73, Rarity.RARE, mage.cards.b.Bloodthirster.class)); - cards.add(new SetCardInfo("Bone Sabres", 88, Rarity.RARE, mage.cards.b.BoneSabres.class)); - cards.add(new SetCardInfo("Brainstorm", 192, Rarity.COMMON, mage.cards.b.Brainstorm.class)); - cards.add(new SetCardInfo("Bred for the Hunt", 222, Rarity.UNCOMMON, mage.cards.b.BredForTheHunt.class)); - cards.add(new SetCardInfo("Broodlord", 89, Rarity.RARE, mage.cards.b.Broodlord.class)); - cards.add(new SetCardInfo("Caged Sun", 231, Rarity.RARE, mage.cards.c.CagedSun.class)); - cards.add(new SetCardInfo("Callidus Assassin", 109, Rarity.RARE, mage.cards.c.CallidusAssassin.class)); - cards.add(new SetCardInfo("Canoptek Scarab Swarm", 150, Rarity.RARE, mage.cards.c.CanoptekScarabSwarm.class)); - cards.add(new SetCardInfo("Canoptek Spyder", 151, Rarity.RARE, mage.cards.c.CanoptekSpyder.class)); - cards.add(new SetCardInfo("Canoptek Tomb Sentinel", 152, Rarity.RARE, mage.cards.c.CanoptekTombSentinel.class)); - cards.add(new SetCardInfo("Canoptek Wraith", 153, Rarity.RARE, mage.cards.c.CanoptekWraith.class)); - cards.add(new SetCardInfo("Cave of Temptation", 267, Rarity.COMMON, mage.cards.c.CaveOfTemptation.class)); - cards.add(new SetCardInfo("Celestine, the Living Saint", 10, Rarity.RARE, mage.cards.c.CelestineTheLivingSaint.class)); - cards.add(new SetCardInfo("Chaos Defiler", 110, Rarity.RARE, mage.cards.c.ChaosDefiler.class)); - cards.add(new SetCardInfo("Chaos Mutation", 111, Rarity.RARE, mage.cards.c.ChaosMutation.class)); - cards.add(new SetCardInfo("Chaos Terminator Lord", 74, Rarity.UNCOMMON, mage.cards.c.ChaosTerminatorLord.class)); - cards.add(new SetCardInfo("Chaos Warp", 205, Rarity.RARE, mage.cards.c.ChaosWarp.class)); - cards.add(new SetCardInfo("Choked Estuary", 268, Rarity.RARE, mage.cards.c.ChokedEstuary.class)); - cards.add(new SetCardInfo("Chromatic Lantern", 232, Rarity.RARE, mage.cards.c.ChromaticLantern.class)); - cards.add(new SetCardInfo("Chronomancer", 32, Rarity.RARE, mage.cards.c.Chronomancer.class)); - cards.add(new SetCardInfo("Cinder Glade", 269, Rarity.RARE, mage.cards.c.CinderGlade.class)); - cards.add(new SetCardInfo("Clamavus", 90, Rarity.RARE, mage.cards.c.Clamavus.class)); - cards.add(new SetCardInfo("Collective Effort", 183, Rarity.RARE, mage.cards.c.CollectiveEffort.class)); - cards.add(new SetCardInfo("Command Tower", 270, Rarity.COMMON, mage.cards.c.CommandTower.class)); - cards.add(new SetCardInfo("Commander's Sphere", 233, Rarity.COMMON, mage.cards.c.CommandersSphere.class)); - cards.add(new SetCardInfo("Commissar Severina Raine", 112, Rarity.RARE, mage.cards.c.CommissarSeverinaRaine.class)); - cards.add(new SetCardInfo("Company Commander", 113, Rarity.RARE, mage.cards.c.CompanyCommander.class)); - cards.add(new SetCardInfo("Convergence of Dominion", 154, Rarity.RARE, mage.cards.c.ConvergenceOfDominion.class)); - cards.add(new SetCardInfo("Cranial Plating", 236, Rarity.UNCOMMON, mage.cards.c.CranialPlating.class)); - cards.add(new SetCardInfo("Crumbling Necropolis", 273, Rarity.UNCOMMON, mage.cards.c.CrumblingNecropolis.class)); - cards.add(new SetCardInfo("Cryptek", 33, Rarity.RARE, mage.cards.c.Cryptek.class)); - cards.add(new SetCardInfo("Cryptothrall", 155, Rarity.RARE, mage.cards.c.Cryptothrall.class)); - cards.add(new SetCardInfo("Cultivate", 211, Rarity.COMMON, mage.cards.c.Cultivate.class)); - cards.add(new SetCardInfo("Cybernetica Datasmith", 114, Rarity.RARE, mage.cards.c.CyberneticaDatasmith.class)); - cards.add(new SetCardInfo("Dark Apostle", 75, Rarity.RARE, mage.cards.d.DarkApostle.class)); - cards.add(new SetCardInfo("Dark Ritual", 196, Rarity.COMMON, mage.cards.d.DarkRitual.class)); - cards.add(new SetCardInfo("Darkness", 197, Rarity.COMMON, mage.cards.d.Darkness.class)); - cards.add(new SetCardInfo("Darkwater Catacombs", 274, Rarity.RARE, mage.cards.d.DarkwaterCatacombs.class)); - cards.add(new SetCardInfo("Death's Presence", 212, Rarity.RARE, mage.cards.d.DeathsPresence.class)); - cards.add(new SetCardInfo("Deathleaper, Terror Weapon", 115, Rarity.RARE, mage.cards.d.DeathleaperTerrorWeapon.class)); - cards.add(new SetCardInfo("Decree of Pain", 198, Rarity.RARE, mage.cards.d.DecreeOfPain.class)); - cards.add(new SetCardInfo("Defenders of Humanity", 11, Rarity.RARE, mage.cards.d.DefendersOfHumanity.class)); - cards.add(new SetCardInfo("Defile", 199, Rarity.UNCOMMON, mage.cards.d.Defile.class)); - cards.add(new SetCardInfo("Deny Reality", 223, Rarity.COMMON, mage.cards.d.DenyReality.class)); - cards.add(new SetCardInfo("Deny the Witch", 116, Rarity.UNCOMMON, mage.cards.d.DenyTheWitch.class)); - cards.add(new SetCardInfo("Deploy to the Front", 184, Rarity.RARE, mage.cards.d.DeployToTheFront.class)); - cards.add(new SetCardInfo("Desert of the Glorified", 275, Rarity.COMMON, mage.cards.d.DesertOfTheGlorified.class)); - cards.add(new SetCardInfo("Dismal Backwater", 276, Rarity.COMMON, mage.cards.d.DismalBackwater.class)); - cards.add(new SetCardInfo("Drach'Nyen", 117, Rarity.RARE, mage.cards.d.DrachNyen.class)); - cards.add(new SetCardInfo("Dread Return", 200, Rarity.UNCOMMON, mage.cards.d.DreadReturn.class)); - cards.add(new SetCardInfo("Endless Atlas", 237, Rarity.RARE, mage.cards.e.EndlessAtlas.class)); - cards.add(new SetCardInfo("Entrapment Maneuver", 185, Rarity.RARE, mage.cards.e.EntrapmentManeuver.class)); - cards.add(new SetCardInfo("Epistolary Librarian", 118, Rarity.RARE, mage.cards.e.EpistolaryLibrarian.class)); - cards.add(new SetCardInfo("Everflowing Chalice", 238, Rarity.RARE, mage.cards.e.EverflowingChalice.class)); - cards.add(new SetCardInfo("Evolving Wilds", 277, Rarity.COMMON, mage.cards.e.EvolvingWilds.class)); - cards.add(new SetCardInfo("Exalted Flamer of Tzeentch", 119, Rarity.RARE, mage.cards.e.ExaltedFlamerOfTzeentch.class)); - cards.add(new SetCardInfo("Exocrine", 76, Rarity.RARE, mage.cards.e.Exocrine.class)); - cards.add(new SetCardInfo("Exotic Orchard", 278, Rarity.RARE, mage.cards.e.ExoticOrchard.class)); - cards.add(new SetCardInfo("Explore", 213, Rarity.COMMON, mage.cards.e.Explore.class)); - cards.add(new SetCardInfo("Exterminatus", 120, Rarity.RARE, mage.cards.e.Exterminatus.class)); + cards.add(new SetCardInfo("Abaddon the Despoiler", 171, Rarity.MYTHIC, mage.cards.a.AbaddonTheDespoiler.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Abaddon the Despoiler", 178, Rarity.MYTHIC, mage.cards.a.AbaddonTheDespoiler.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Abaddon the Despoiler", 2, Rarity.MYTHIC, mage.cards.a.AbaddonTheDespoiler.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Abaddon the Despoiler", 319, Rarity.MYTHIC, mage.cards.a.AbaddonTheDespoiler.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Aberrant", "86*", Rarity.UNCOMMON, mage.cards.a.Aberrant.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Aberrant", 86, Rarity.UNCOMMON, mage.cards.a.Aberrant.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Abundance", "210*", Rarity.RARE, mage.cards.a.Abundance.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Abundance", 210, Rarity.RARE, mage.cards.a.Abundance.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Acolyte Hybrid", "70*", Rarity.UNCOMMON, mage.cards.a.AcolyteHybrid.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Acolyte Hybrid", 70, Rarity.UNCOMMON, mage.cards.a.AcolyteHybrid.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Adaptive Automaton", 322, Rarity.RARE, mage.cards.a.AdaptiveAutomaton.class)); + cards.add(new SetCardInfo("Aetherize", "191*", Rarity.UNCOMMON, mage.cards.a.Aetherize.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Aetherize", 191, Rarity.UNCOMMON, mage.cards.a.Aetherize.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("And They Shall Know No Fear", "9*", Rarity.UNCOMMON, mage.cards.a.AndTheyShallKnowNoFear.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("And They Shall Know No Fear", 9, Rarity.UNCOMMON, mage.cards.a.AndTheyShallKnowNoFear.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Anrakyr the Traveller", "28*", Rarity.RARE, mage.cards.a.AnrakyrTheTraveller.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Anrakyr the Traveller", 28, Rarity.RARE, mage.cards.a.AnrakyrTheTraveller.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Arcane Sanctum", "264*", Rarity.UNCOMMON, mage.cards.a.ArcaneSanctum.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Arcane Sanctum", 264, Rarity.UNCOMMON, mage.cards.a.ArcaneSanctum.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Arcane Signet", "227*", Rarity.COMMON, mage.cards.a.ArcaneSignet.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Arcane Signet", "228*", Rarity.COMMON, mage.cards.a.ArcaneSignet.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Arcane Signet", "229*", Rarity.COMMON, mage.cards.a.ArcaneSignet.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Arcane Signet", 227, Rarity.COMMON, mage.cards.a.ArcaneSignet.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Arcane Signet", 228, Rarity.COMMON, mage.cards.a.ArcaneSignet.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Arcane Signet", 229, Rarity.COMMON, mage.cards.a.ArcaneSignet.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Arco-Flagellant", "29*", Rarity.RARE, mage.cards.a.ArcoFlagellant.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Arco-Flagellant", 29, Rarity.RARE, mage.cards.a.ArcoFlagellant.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ash Barrens", "265*", Rarity.UNCOMMON, mage.cards.a.AshBarrens.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ash Barrens", 265, Rarity.UNCOMMON, mage.cards.a.AshBarrens.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Aspiring Champion", "71*", Rarity.RARE, mage.cards.a.AspiringChampion.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Aspiring Champion", 71, Rarity.RARE, mage.cards.a.AspiringChampion.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Assault Intercessor", "104*", Rarity.RARE, mage.cards.a.AssaultIntercessor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Assault Intercessor", 104, Rarity.RARE, mage.cards.a.AssaultIntercessor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Assault Suit", "230*", Rarity.UNCOMMON, mage.cards.a.AssaultSuit.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Assault Suit", 230, Rarity.UNCOMMON, mage.cards.a.AssaultSuit.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Atalan Jackal", "105*", Rarity.RARE, mage.cards.a.AtalanJackal.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Atalan Jackal", 105, Rarity.RARE, mage.cards.a.AtalanJackal.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Barren Moor", "266*", Rarity.UNCOMMON, mage.cards.b.BarrenMoor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Barren Moor", 266, Rarity.UNCOMMON, mage.cards.b.BarrenMoor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bastion Protector", "182*", Rarity.RARE, mage.cards.b.BastionProtector.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bastion Protector", 182, Rarity.RARE, mage.cards.b.BastionProtector.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Be'lakor, the Dark Master", 172, Rarity.MYTHIC, mage.cards.b.BelakorTheDarkMaster.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Be'lakor, the Dark Master", 6, Rarity.MYTHIC, mage.cards.b.BelakorTheDarkMaster.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Beacon of Unrest", "194*", Rarity.RARE, mage.cards.b.BeaconOfUnrest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Beacon of Unrest", 194, Rarity.RARE, mage.cards.b.BeaconOfUnrest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Belisarius Cawl", "106*", Rarity.RARE, mage.cards.b.BelisariusCawl.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Belisarius Cawl", 106, Rarity.RARE, mage.cards.b.BelisariusCawl.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bile Blight", "195*", Rarity.UNCOMMON, mage.cards.b.BileBlight.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bile Blight", 195, Rarity.UNCOMMON, mage.cards.b.BileBlight.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Biophagus", "87*", Rarity.RARE, mage.cards.b.Biophagus.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Biophagus", 87, Rarity.RARE, mage.cards.b.Biophagus.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Biotransference", "30*", Rarity.RARE, mage.cards.b.Biotransference.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Biotransference", 30, Rarity.RARE, mage.cards.b.Biotransference.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Birth of the Imperium", "107*", Rarity.RARE, mage.cards.b.BirthOfTheImperium.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Birth of the Imperium", 107, Rarity.RARE, mage.cards.b.BirthOfTheImperium.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bituminous Blast", "221*", Rarity.UNCOMMON, mage.cards.b.BituminousBlast.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bituminous Blast", 221, Rarity.UNCOMMON, mage.cards.b.BituminousBlast.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Blasphemous Act", "204*", Rarity.RARE, mage.cards.b.BlasphemousAct.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Blasphemous Act", 204, Rarity.RARE, mage.cards.b.BlasphemousAct.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Blight Grenade", "31*", Rarity.RARE, mage.cards.b.BlightGrenade.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Blight Grenade", 31, Rarity.RARE, mage.cards.b.BlightGrenade.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Blood for the Blood God!", "108*", Rarity.RARE, mage.cards.b.BloodForTheBloodGod.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Blood for the Blood God!", 108, Rarity.RARE, mage.cards.b.BloodForTheBloodGod.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bloodcrusher of Khorne", "72*", Rarity.UNCOMMON, mage.cards.b.BloodcrusherOfKhorne.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bloodcrusher of Khorne", 72, Rarity.UNCOMMON, mage.cards.b.BloodcrusherOfKhorne.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bloodthirster", "73*", Rarity.RARE, mage.cards.b.Bloodthirster.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bloodthirster", 73, Rarity.RARE, mage.cards.b.Bloodthirster.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bone Sabres", "88*", Rarity.RARE, mage.cards.b.BoneSabres.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bone Sabres", 88, Rarity.RARE, mage.cards.b.BoneSabres.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Brainstorm", "192*", Rarity.COMMON, mage.cards.b.Brainstorm.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Brainstorm", 192, Rarity.COMMON, mage.cards.b.Brainstorm.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bred for the Hunt", "222*", Rarity.UNCOMMON, mage.cards.b.BredForTheHunt.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bred for the Hunt", 222, Rarity.UNCOMMON, mage.cards.b.BredForTheHunt.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Broodlord", "89*", Rarity.RARE, mage.cards.b.Broodlord.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Broodlord", 89, Rarity.RARE, mage.cards.b.Broodlord.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Caged Sun", "231*", Rarity.RARE, mage.cards.c.CagedSun.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Caged Sun", 231, Rarity.RARE, mage.cards.c.CagedSun.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Callidus Assassin", "109*", Rarity.RARE, mage.cards.c.CallidusAssassin.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Callidus Assassin", 109, Rarity.RARE, mage.cards.c.CallidusAssassin.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Canoptek Scarab Swarm", "150*", Rarity.RARE, mage.cards.c.CanoptekScarabSwarm.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Canoptek Scarab Swarm", 150, Rarity.RARE, mage.cards.c.CanoptekScarabSwarm.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Canoptek Spyder", "151*", Rarity.RARE, mage.cards.c.CanoptekSpyder.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Canoptek Spyder", 151, Rarity.RARE, mage.cards.c.CanoptekSpyder.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Canoptek Tomb Sentinel", "152*", Rarity.RARE, mage.cards.c.CanoptekTombSentinel.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Canoptek Tomb Sentinel", 152, Rarity.RARE, mage.cards.c.CanoptekTombSentinel.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Canoptek Wraith", "153*", Rarity.RARE, mage.cards.c.CanoptekWraith.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Canoptek Wraith", 153, Rarity.RARE, mage.cards.c.CanoptekWraith.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cave of Temptation", "267*", Rarity.COMMON, mage.cards.c.CaveOfTemptation.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cave of Temptation", 267, Rarity.COMMON, mage.cards.c.CaveOfTemptation.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Celestine, the Living Saint", "10*", Rarity.RARE, mage.cards.c.CelestineTheLivingSaint.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Celestine, the Living Saint", 10, Rarity.RARE, mage.cards.c.CelestineTheLivingSaint.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Chaos Defiler", "110*", Rarity.RARE, mage.cards.c.ChaosDefiler.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Chaos Defiler", 110, Rarity.RARE, mage.cards.c.ChaosDefiler.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Chaos Mutation", "111*", Rarity.RARE, mage.cards.c.ChaosMutation.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Chaos Mutation", 111, Rarity.RARE, mage.cards.c.ChaosMutation.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Chaos Terminator Lord", "74*", Rarity.UNCOMMON, mage.cards.c.ChaosTerminatorLord.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Chaos Terminator Lord", 74, Rarity.UNCOMMON, mage.cards.c.ChaosTerminatorLord.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Chaos Warp", "205*", Rarity.RARE, mage.cards.c.ChaosWarp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Chaos Warp", 205, Rarity.RARE, mage.cards.c.ChaosWarp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Choked Estuary", "268*", Rarity.RARE, mage.cards.c.ChokedEstuary.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Choked Estuary", 268, Rarity.RARE, mage.cards.c.ChokedEstuary.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Chromatic Lantern", "232*", Rarity.RARE, mage.cards.c.ChromaticLantern.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Chromatic Lantern", 232, Rarity.RARE, mage.cards.c.ChromaticLantern.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Chronomancer", "32*", Rarity.RARE, mage.cards.c.Chronomancer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Chronomancer", 32, Rarity.RARE, mage.cards.c.Chronomancer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cinder Glade", "269*", Rarity.RARE, mage.cards.c.CinderGlade.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cinder Glade", 269, Rarity.RARE, mage.cards.c.CinderGlade.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Clamavus", "90*", Rarity.RARE, mage.cards.c.Clamavus.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Clamavus", 90, Rarity.RARE, mage.cards.c.Clamavus.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Collective Effort", "183*", Rarity.RARE, mage.cards.c.CollectiveEffort.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Collective Effort", 183, Rarity.RARE, mage.cards.c.CollectiveEffort.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Command Tower", "270*", Rarity.COMMON, mage.cards.c.CommandTower.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Command Tower", "271*", Rarity.COMMON, mage.cards.c.CommandTower.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Command Tower", "272*", Rarity.COMMON, mage.cards.c.CommandTower.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Command Tower", 270, Rarity.COMMON, mage.cards.c.CommandTower.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Command Tower", 271, Rarity.COMMON, mage.cards.c.CommandTower.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Command Tower", 272, Rarity.COMMON, mage.cards.c.CommandTower.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Commander's Sphere", "233*", Rarity.COMMON, mage.cards.c.CommandersSphere.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Commander's Sphere", "234*", Rarity.COMMON, mage.cards.c.CommandersSphere.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Commander's Sphere", "235*", Rarity.COMMON, mage.cards.c.CommandersSphere.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Commander's Sphere", 233, Rarity.COMMON, mage.cards.c.CommandersSphere.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Commander's Sphere", 234, Rarity.COMMON, mage.cards.c.CommandersSphere.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Commander's Sphere", 235, Rarity.COMMON, mage.cards.c.CommandersSphere.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Commissar Severina Raine", "112*", Rarity.RARE, mage.cards.c.CommissarSeverinaRaine.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Commissar Severina Raine", 112, Rarity.RARE, mage.cards.c.CommissarSeverinaRaine.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Company Commander", "113*", Rarity.RARE, mage.cards.c.CompanyCommander.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Company Commander", 113, Rarity.RARE, mage.cards.c.CompanyCommander.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Convergence of Dominion", "154*", Rarity.RARE, mage.cards.c.ConvergenceOfDominion.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Convergence of Dominion", 154, Rarity.RARE, mage.cards.c.ConvergenceOfDominion.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cranial Plating", "236*", Rarity.UNCOMMON, mage.cards.c.CranialPlating.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cranial Plating", 236, Rarity.UNCOMMON, mage.cards.c.CranialPlating.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Crumbling Necropolis", "273*", Rarity.UNCOMMON, mage.cards.c.CrumblingNecropolis.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Crumbling Necropolis", 273, Rarity.UNCOMMON, mage.cards.c.CrumblingNecropolis.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cryptek", "33*", Rarity.RARE, mage.cards.c.Cryptek.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cryptek", 33, Rarity.RARE, mage.cards.c.Cryptek.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cryptothrall", "155*", Rarity.RARE, mage.cards.c.Cryptothrall.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cryptothrall", 155, Rarity.RARE, mage.cards.c.Cryptothrall.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cultivate", "211*", Rarity.COMMON, mage.cards.c.Cultivate.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cultivate", 211, Rarity.COMMON, mage.cards.c.Cultivate.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cybernetica Datasmith", "114*", Rarity.RARE, mage.cards.c.CyberneticaDatasmith.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cybernetica Datasmith", 114, Rarity.RARE, mage.cards.c.CyberneticaDatasmith.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Dark Apostle", "75*", Rarity.RARE, mage.cards.d.DarkApostle.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Dark Apostle", 75, Rarity.RARE, mage.cards.d.DarkApostle.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Dark Ritual", "196*", Rarity.COMMON, mage.cards.d.DarkRitual.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Dark Ritual", 196, Rarity.COMMON, mage.cards.d.DarkRitual.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Darkness", "197*", Rarity.COMMON, mage.cards.d.Darkness.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Darkness", 197, Rarity.COMMON, mage.cards.d.Darkness.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Darkwater Catacombs", "274*", Rarity.RARE, mage.cards.d.DarkwaterCatacombs.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Darkwater Catacombs", 274, Rarity.RARE, mage.cards.d.DarkwaterCatacombs.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Death's Presence", "212*", Rarity.RARE, mage.cards.d.DeathsPresence.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Death's Presence", 212, Rarity.RARE, mage.cards.d.DeathsPresence.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Deathleaper, Terror Weapon", "115*", Rarity.RARE, mage.cards.d.DeathleaperTerrorWeapon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Deathleaper, Terror Weapon", 115, Rarity.RARE, mage.cards.d.DeathleaperTerrorWeapon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Decree of Pain", "198*", Rarity.RARE, mage.cards.d.DecreeOfPain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Decree of Pain", 198, Rarity.RARE, mage.cards.d.DecreeOfPain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Defenders of Humanity", "11*", Rarity.RARE, mage.cards.d.DefendersOfHumanity.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Defenders of Humanity", 11, Rarity.RARE, mage.cards.d.DefendersOfHumanity.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Defile", "199*", Rarity.UNCOMMON, mage.cards.d.Defile.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Defile", 199, Rarity.UNCOMMON, mage.cards.d.Defile.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Deny Reality", "223*", Rarity.COMMON, mage.cards.d.DenyReality.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Deny Reality", 223, Rarity.COMMON, mage.cards.d.DenyReality.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Deny the Witch", "116*", Rarity.UNCOMMON, mage.cards.d.DenyTheWitch.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Deny the Witch", 116, Rarity.UNCOMMON, mage.cards.d.DenyTheWitch.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Deploy to the Front", "184*", Rarity.RARE, mage.cards.d.DeployToTheFront.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Deploy to the Front", 184, Rarity.RARE, mage.cards.d.DeployToTheFront.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Desert of the Glorified", "275*", Rarity.COMMON, mage.cards.d.DesertOfTheGlorified.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Desert of the Glorified", 275, Rarity.COMMON, mage.cards.d.DesertOfTheGlorified.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Dismal Backwater", "276*", Rarity.COMMON, mage.cards.d.DismalBackwater.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Dismal Backwater", 276, Rarity.COMMON, mage.cards.d.DismalBackwater.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Drach'Nyen", "117*", Rarity.RARE, mage.cards.d.DrachNyen.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Drach'Nyen", 117, Rarity.RARE, mage.cards.d.DrachNyen.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Dread Return", "200*", Rarity.UNCOMMON, mage.cards.d.DreadReturn.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Dread Return", 200, Rarity.UNCOMMON, mage.cards.d.DreadReturn.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Endless Atlas", "237*", Rarity.RARE, mage.cards.e.EndlessAtlas.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Endless Atlas", 237, Rarity.RARE, mage.cards.e.EndlessAtlas.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Entrapment Maneuver", "185*", Rarity.RARE, mage.cards.e.EntrapmentManeuver.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Entrapment Maneuver", 185, Rarity.RARE, mage.cards.e.EntrapmentManeuver.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Epistolary Librarian", "118*", Rarity.RARE, mage.cards.e.EpistolaryLibrarian.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Epistolary Librarian", 118, Rarity.RARE, mage.cards.e.EpistolaryLibrarian.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Everflowing Chalice", "238*", Rarity.RARE, mage.cards.e.EverflowingChalice.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Everflowing Chalice", 238, Rarity.RARE, mage.cards.e.EverflowingChalice.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Evolving Wilds", "277*", Rarity.COMMON, mage.cards.e.EvolvingWilds.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Evolving Wilds", 277, Rarity.COMMON, mage.cards.e.EvolvingWilds.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Exalted Flamer of Tzeentch", "119*", Rarity.RARE, mage.cards.e.ExaltedFlamerOfTzeentch.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Exalted Flamer of Tzeentch", 119, Rarity.RARE, mage.cards.e.ExaltedFlamerOfTzeentch.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Exocrine", "76*", Rarity.RARE, mage.cards.e.Exocrine.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Exocrine", 76, Rarity.RARE, mage.cards.e.Exocrine.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Exotic Orchard", "278*", Rarity.RARE, mage.cards.e.ExoticOrchard.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Exotic Orchard", 278, Rarity.RARE, mage.cards.e.ExoticOrchard.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Explore", "213*", Rarity.COMMON, mage.cards.e.Explore.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Explore", 213, Rarity.COMMON, mage.cards.e.Explore.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Exterminatus", "120*", Rarity.RARE, mage.cards.e.Exterminatus.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Exterminatus", 120, Rarity.RARE, mage.cards.e.Exterminatus.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Fabricate", 181, Rarity.RARE, mage.cards.f.Fabricate.class)); - cards.add(new SetCardInfo("Farseek", 214, Rarity.COMMON, mage.cards.f.Farseek.class)); - cards.add(new SetCardInfo("Fell the Mighty", 186, Rarity.RARE, mage.cards.f.FellTheMighty.class)); - cards.add(new SetCardInfo("Flayed One", 34, Rarity.UNCOMMON, mage.cards.f.FlayedOne.class)); - cards.add(new SetCardInfo("For the Emperor!", 12, Rarity.RARE, mage.cards.f.ForTheEmperor.class)); - cards.add(new SetCardInfo("Foreboding Ruins", 279, Rarity.RARE, mage.cards.f.ForebodingRuins.class)); + cards.add(new SetCardInfo("Farseek", "214*", Rarity.COMMON, mage.cards.f.Farseek.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Farseek", 214, Rarity.COMMON, mage.cards.f.Farseek.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Fell the Mighty", "186*", Rarity.RARE, mage.cards.f.FellTheMighty.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Fell the Mighty", 186, Rarity.RARE, mage.cards.f.FellTheMighty.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Flayed One", "34*", Rarity.UNCOMMON, mage.cards.f.FlayedOne.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Flayed One", 34, Rarity.UNCOMMON, mage.cards.f.FlayedOne.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("For the Emperor!", "12*", Rarity.RARE, mage.cards.f.ForTheEmperor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("For the Emperor!", 12, Rarity.RARE, mage.cards.f.ForTheEmperor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Foreboding Ruins", "279*", Rarity.RARE, mage.cards.f.ForebodingRuins.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Foreboding Ruins", 279, Rarity.RARE, mage.cards.f.ForebodingRuins.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Forest", "317*", Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Forest", 317, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Forgotten Cave", 280, Rarity.COMMON, mage.cards.f.ForgottenCave.class)); - cards.add(new SetCardInfo("Frontier Bivouac", 281, Rarity.UNCOMMON, mage.cards.f.FrontierBivouac.class)); - cards.add(new SetCardInfo("Game Trail", 282, Rarity.RARE, mage.cards.g.GameTrail.class)); - cards.add(new SetCardInfo("Gargoyle Flock", 123, Rarity.RARE, mage.cards.g.GargoyleFlock.class)); - cards.add(new SetCardInfo("Genestealer Locus", 21, Rarity.UNCOMMON, mage.cards.g.GenestealerLocus.class)); - cards.add(new SetCardInfo("Genestealer Patriarch", 22, Rarity.RARE, mage.cards.g.GenestealerPatriarch.class)); - cards.add(new SetCardInfo("Ghost Ark", 156, Rarity.RARE, mage.cards.g.GhostArk.class)); - cards.add(new SetCardInfo("Ghyrson Starn, Kelermorph", 124, Rarity.RARE, mage.cards.g.GhyrsonStarnKelermorph.class)); - cards.add(new SetCardInfo("Gilded Lotus", 239, Rarity.RARE, mage.cards.g.GildedLotus.class)); - cards.add(new SetCardInfo("Go for the Throat", 201, Rarity.COMMON, mage.cards.g.GoForTheThroat.class)); - cards.add(new SetCardInfo("Goliath Truck", 158, Rarity.UNCOMMON, mage.cards.g.GoliathTruck.class)); - cards.add(new SetCardInfo("Great Unclean One", 35, Rarity.RARE, mage.cards.g.GreatUncleanOne.class)); - cards.add(new SetCardInfo("Grey Knight Paragon", 13, Rarity.UNCOMMON, mage.cards.g.GreyKnightParagon.class)); - cards.add(new SetCardInfo("Hardened Scales", 215, Rarity.RARE, mage.cards.h.HardenedScales.class)); - cards.add(new SetCardInfo("Harrow", 216, Rarity.COMMON, mage.cards.h.Harrow.class)); - cards.add(new SetCardInfo("Haruspex", 91, Rarity.RARE, mage.cards.h.Haruspex.class)); - cards.add(new SetCardInfo("Hedron Archive", 240, Rarity.UNCOMMON, mage.cards.h.HedronArchive.class)); - cards.add(new SetCardInfo("Helbrute", 125, Rarity.RARE, mage.cards.h.Helbrute.class)); - cards.add(new SetCardInfo("Herald of Slaanesh", 77, Rarity.UNCOMMON, mage.cards.h.HeraldOfSlaanesh.class)); - cards.add(new SetCardInfo("Herald's Horn", 241, Rarity.UNCOMMON, mage.cards.h.HeraldsHorn.class)); - cards.add(new SetCardInfo("Heralds of Tzeentch", 23, Rarity.UNCOMMON, mage.cards.h.HeraldsOfTzeentch.class)); - cards.add(new SetCardInfo("Hexmark Destroyer", 36, Rarity.UNCOMMON, mage.cards.h.HexmarkDestroyer.class)); - cards.add(new SetCardInfo("Hierophant Bio-Titan", 92, Rarity.RARE, mage.cards.h.HierophantBioTitan.class)); - cards.add(new SetCardInfo("Hormagaunt Horde", 93, Rarity.RARE, mage.cards.h.HormagauntHorde.class)); - cards.add(new SetCardInfo("Hour of Reckoning", 187, Rarity.RARE, mage.cards.h.HourOfReckoning.class)); - cards.add(new SetCardInfo("Hull Breach", 224, Rarity.UNCOMMON, mage.cards.h.HullBreach.class)); - cards.add(new SetCardInfo("Icon of Ancestry", 242, Rarity.RARE, mage.cards.i.IconOfAncestry.class)); - cards.add(new SetCardInfo("Illuminor Szeras", 37, Rarity.RARE, mage.cards.i.IlluminorSzeras.class)); - cards.add(new SetCardInfo("Imotekh the Stormlord", 5, Rarity.MYTHIC, mage.cards.i.ImotekhTheStormlord.class)); - cards.add(new SetCardInfo("Inquisitor Eisenhorn", 127, Rarity.RARE, mage.cards.i.InquisitorEisenhorn.class)); - cards.add(new SetCardInfo("Inquisitor Greyfax", 3, Rarity.MYTHIC, mage.cards.i.InquisitorGreyfax.class)); - cards.add(new SetCardInfo("Inquisitorial Rosette", 159, Rarity.RARE, mage.cards.i.InquisitorialRosette.class)); - cards.add(new SetCardInfo("Inspiring Call", 217, Rarity.UNCOMMON, mage.cards.i.InspiringCall.class)); + cards.add(new SetCardInfo("Forgotten Cave", "280*", Rarity.COMMON, mage.cards.f.ForgottenCave.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Forgotten Cave", 280, Rarity.COMMON, mage.cards.f.ForgottenCave.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Frontier Bivouac", "281*", Rarity.UNCOMMON, mage.cards.f.FrontierBivouac.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Frontier Bivouac", 281, Rarity.UNCOMMON, mage.cards.f.FrontierBivouac.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Game Trail", "282*", Rarity.RARE, mage.cards.g.GameTrail.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Game Trail", 282, Rarity.RARE, mage.cards.g.GameTrail.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Gargoyle Flock", "123*", Rarity.RARE, mage.cards.g.GargoyleFlock.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Gargoyle Flock", 123, Rarity.RARE, mage.cards.g.GargoyleFlock.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Genestealer Locus", "21*", Rarity.UNCOMMON, mage.cards.g.GenestealerLocus.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Genestealer Locus", 21, Rarity.UNCOMMON, mage.cards.g.GenestealerLocus.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Genestealer Patriarch", "22*", Rarity.RARE, mage.cards.g.GenestealerPatriarch.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Genestealer Patriarch", 22, Rarity.RARE, mage.cards.g.GenestealerPatriarch.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ghost Ark", "156*", Rarity.RARE, mage.cards.g.GhostArk.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ghost Ark", 156, Rarity.RARE, mage.cards.g.GhostArk.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ghyrson Starn, Kelermorph", "124*", Rarity.RARE, mage.cards.g.GhyrsonStarnKelermorph.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ghyrson Starn, Kelermorph", 124, Rarity.RARE, mage.cards.g.GhyrsonStarnKelermorph.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Gilded Lotus", "239*", Rarity.RARE, mage.cards.g.GildedLotus.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Gilded Lotus", 239, Rarity.RARE, mage.cards.g.GildedLotus.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Go for the Throat", "201*", Rarity.COMMON, mage.cards.g.GoForTheThroat.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Go for the Throat", 201, Rarity.COMMON, mage.cards.g.GoForTheThroat.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Goliath Truck", "158*", Rarity.UNCOMMON, mage.cards.g.GoliathTruck.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Goliath Truck", 158, Rarity.UNCOMMON, mage.cards.g.GoliathTruck.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Great Unclean One", "35*", Rarity.RARE, mage.cards.g.GreatUncleanOne.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Great Unclean One", 35, Rarity.RARE, mage.cards.g.GreatUncleanOne.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Grey Knight Paragon", "13*", Rarity.UNCOMMON, mage.cards.g.GreyKnightParagon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Grey Knight Paragon", 13, Rarity.UNCOMMON, mage.cards.g.GreyKnightParagon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hardened Scales", "215*", Rarity.RARE, mage.cards.h.HardenedScales.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hardened Scales", 215, Rarity.RARE, mage.cards.h.HardenedScales.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Harrow", "216*", Rarity.COMMON, mage.cards.h.Harrow.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Harrow", 216, Rarity.COMMON, mage.cards.h.Harrow.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Haruspex", "91*", Rarity.RARE, mage.cards.h.Haruspex.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Haruspex", 91, Rarity.RARE, mage.cards.h.Haruspex.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hedron Archive", "240*", Rarity.UNCOMMON, mage.cards.h.HedronArchive.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hedron Archive", 240, Rarity.UNCOMMON, mage.cards.h.HedronArchive.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Helbrute", "125*", Rarity.RARE, mage.cards.h.Helbrute.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Helbrute", 125, Rarity.RARE, mage.cards.h.Helbrute.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Herald of Slaanesh", "77*", Rarity.UNCOMMON, mage.cards.h.HeraldOfSlaanesh.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Herald of Slaanesh", 77, Rarity.UNCOMMON, mage.cards.h.HeraldOfSlaanesh.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Herald's Horn", "241*", Rarity.UNCOMMON, mage.cards.h.HeraldsHorn.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Herald's Horn", 241, Rarity.UNCOMMON, mage.cards.h.HeraldsHorn.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Heralds of Tzeentch", "23*", Rarity.UNCOMMON, mage.cards.h.HeraldsOfTzeentch.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Heralds of Tzeentch", 23, Rarity.UNCOMMON, mage.cards.h.HeraldsOfTzeentch.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hexmark Destroyer", "36*", Rarity.UNCOMMON, mage.cards.h.HexmarkDestroyer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hexmark Destroyer", 36, Rarity.UNCOMMON, mage.cards.h.HexmarkDestroyer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hierophant Bio-Titan", "92*", Rarity.RARE, mage.cards.h.HierophantBioTitan.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hierophant Bio-Titan", 92, Rarity.RARE, mage.cards.h.HierophantBioTitan.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hormagaunt Horde", "93*", Rarity.RARE, mage.cards.h.HormagauntHorde.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hormagaunt Horde", 93, Rarity.RARE, mage.cards.h.HormagauntHorde.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hour of Reckoning", "187*", Rarity.RARE, mage.cards.h.HourOfReckoning.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hour of Reckoning", 187, Rarity.RARE, mage.cards.h.HourOfReckoning.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hull Breach", "224*", Rarity.UNCOMMON, mage.cards.h.HullBreach.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hull Breach", 224, Rarity.UNCOMMON, mage.cards.h.HullBreach.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Icon of Ancestry", "242*", Rarity.RARE, mage.cards.i.IconOfAncestry.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Icon of Ancestry", 242, Rarity.RARE, mage.cards.i.IconOfAncestry.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Illuminor Szeras", "37*", Rarity.RARE, mage.cards.i.IlluminorSzeras.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Illuminor Szeras", 37, Rarity.RARE, mage.cards.i.IlluminorSzeras.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Imotekh the Stormlord", 169, Rarity.MYTHIC, mage.cards.i.ImotekhTheStormlord.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Imotekh the Stormlord", 5, Rarity.MYTHIC, mage.cards.i.ImotekhTheStormlord.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Inquisitor Eisenhorn", "127*", Rarity.RARE, mage.cards.i.InquisitorEisenhorn.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Inquisitor Eisenhorn", 127, Rarity.RARE, mage.cards.i.InquisitorEisenhorn.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Inquisitor Greyfax", 173, Rarity.MYTHIC, mage.cards.i.InquisitorGreyfax.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Inquisitor Greyfax", 179, Rarity.MYTHIC, mage.cards.i.InquisitorGreyfax.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Inquisitor Greyfax", 3, Rarity.MYTHIC, mage.cards.i.InquisitorGreyfax.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Inquisitor Greyfax", 320, Rarity.MYTHIC, mage.cards.i.InquisitorGreyfax.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Inquisitorial Rosette", "159*", Rarity.RARE, mage.cards.i.InquisitorialRosette.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Inquisitorial Rosette", 159, Rarity.RARE, mage.cards.i.InquisitorialRosette.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Inspiring Call", "217*", Rarity.UNCOMMON, mage.cards.i.InspiringCall.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Inspiring Call", 217, Rarity.UNCOMMON, mage.cards.i.InspiringCall.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Island", "307*", Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Island", "308*", Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Island", "309*", Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Island", 307, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Keeper of Secrets", 78, Rarity.RARE, mage.cards.k.KeeperOfSecrets.class)); - cards.add(new SetCardInfo("Kharn the Betrayer", 79, Rarity.RARE, mage.cards.k.KharnTheBetrayer.class)); - cards.add(new SetCardInfo("Kill! Maim! Burn!", 128, Rarity.RARE, mage.cards.k.KillMaimBurn.class)); - cards.add(new SetCardInfo("Knight Paladin", 160, Rarity.RARE, mage.cards.k.KnightPaladin.class)); - cards.add(new SetCardInfo("Knight Rampager", 80, Rarity.RARE, mage.cards.k.KnightRampager.class)); - cards.add(new SetCardInfo("Launch the Fleet", 188, Rarity.RARE, mage.cards.l.LaunchTheFleet.class)); - cards.add(new SetCardInfo("Let the Galaxy Burn", 81, Rarity.RARE, mage.cards.l.LetTheGalaxyBurn.class)); - cards.add(new SetCardInfo("Lictor", 94, Rarity.RARE, mage.cards.l.Lictor.class)); - cards.add(new SetCardInfo("Living Death", 202, Rarity.RARE, mage.cards.l.LivingDeath.class)); - cards.add(new SetCardInfo("Lokhust Heavy Destroyer", 38, Rarity.RARE, mage.cards.l.LokhustHeavyDestroyer.class)); - cards.add(new SetCardInfo("Lord of Change", 24, Rarity.RARE, mage.cards.l.LordOfChange.class)); - cards.add(new SetCardInfo("Lychguard", 39, Rarity.RARE, mage.cards.l.Lychguard.class)); - cards.add(new SetCardInfo("Magnus the Red", 131, Rarity.RARE, mage.cards.m.MagnusTheRed.class)); - cards.add(new SetCardInfo("Magus Lucea Kane", 7, Rarity.MYTHIC, mage.cards.m.MagusLuceaKane.class)); - cards.add(new SetCardInfo("Malanthrope", 132, Rarity.RARE, mage.cards.m.Malanthrope.class)); - cards.add(new SetCardInfo("Mandate of Abaddon", 40, Rarity.RARE, mage.cards.m.MandateOfAbaddon.class)); - cards.add(new SetCardInfo("Marneus Calgar", 8, Rarity.MYTHIC, mage.cards.m.MarneusCalgar.class)); - cards.add(new SetCardInfo("Martial Coup", 189, Rarity.RARE, mage.cards.m.MartialCoup.class)); - cards.add(new SetCardInfo("Mask of Memory", 243, Rarity.UNCOMMON, mage.cards.m.MaskOfMemory.class)); - cards.add(new SetCardInfo("Mawloc", 133, Rarity.RARE, mage.cards.m.Mawloc.class)); - cards.add(new SetCardInfo("Memorial to Glory", 283, Rarity.UNCOMMON, mage.cards.m.MemorialToGlory.class)); - cards.add(new SetCardInfo("Mind Stone", 244, Rarity.UNCOMMON, mage.cards.m.MindStone.class)); - cards.add(new SetCardInfo("Molten Slagheap", 284, Rarity.UNCOMMON, mage.cards.m.MoltenSlagheap.class)); - cards.add(new SetCardInfo("Mortarion, Daemon Primarch", 41, Rarity.RARE, mage.cards.m.MortarionDaemonPrimarch.class)); - cards.add(new SetCardInfo("Mortify", 225, Rarity.UNCOMMON, mage.cards.m.Mortify.class)); + cards.add(new SetCardInfo("Island", 308, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Island", 309, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Keeper of Secrets", "78*", Rarity.RARE, mage.cards.k.KeeperOfSecrets.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Keeper of Secrets", 78, Rarity.RARE, mage.cards.k.KeeperOfSecrets.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Kharn the Betrayer", "79*", Rarity.RARE, mage.cards.k.KharnTheBetrayer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Kharn the Betrayer", 79, Rarity.RARE, mage.cards.k.KharnTheBetrayer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Kill! Maim! Burn!", "128*", Rarity.RARE, mage.cards.k.KillMaimBurn.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Kill! Maim! Burn!", 128, Rarity.RARE, mage.cards.k.KillMaimBurn.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Knight Paladin", "160*", Rarity.RARE, mage.cards.k.KnightPaladin.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Knight Paladin", 160, Rarity.RARE, mage.cards.k.KnightPaladin.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Knight Rampager", "80*", Rarity.RARE, mage.cards.k.KnightRampager.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Knight Rampager", 80, Rarity.RARE, mage.cards.k.KnightRampager.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Launch the Fleet", "188*", Rarity.RARE, mage.cards.l.LaunchTheFleet.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Launch the Fleet", 188, Rarity.RARE, mage.cards.l.LaunchTheFleet.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Let the Galaxy Burn", "81*", Rarity.RARE, mage.cards.l.LetTheGalaxyBurn.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Let the Galaxy Burn", 81, Rarity.RARE, mage.cards.l.LetTheGalaxyBurn.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lictor", "94*", Rarity.RARE, mage.cards.l.Lictor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lictor", 94, Rarity.RARE, mage.cards.l.Lictor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Living Death", "202*", Rarity.RARE, mage.cards.l.LivingDeath.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Living Death", 202, Rarity.RARE, mage.cards.l.LivingDeath.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lokhust Heavy Destroyer", "38*", Rarity.RARE, mage.cards.l.LokhustHeavyDestroyer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lokhust Heavy Destroyer", 38, Rarity.RARE, mage.cards.l.LokhustHeavyDestroyer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lord of Change", "24*", Rarity.RARE, mage.cards.l.LordOfChange.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lord of Change", 24, Rarity.RARE, mage.cards.l.LordOfChange.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lucius the Eternal", "130*", Rarity.RARE, mage.cards.l.LuciusTheEternal.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lucius the Eternal", 130, Rarity.RARE, mage.cards.l.LuciusTheEternal.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lychguard", "39*", Rarity.RARE, mage.cards.l.Lychguard.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lychguard", 39, Rarity.RARE, mage.cards.l.Lychguard.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Magnus the Red", "131*", Rarity.RARE, mage.cards.m.MagnusTheRed.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Magnus the Red", 131, Rarity.RARE, mage.cards.m.MagnusTheRed.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Magus Lucea Kane", 174, Rarity.MYTHIC, mage.cards.m.MagusLuceaKane.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Magus Lucea Kane", 7, Rarity.MYTHIC, mage.cards.m.MagusLuceaKane.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Malanthrope", "132*", Rarity.RARE, mage.cards.m.Malanthrope.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Malanthrope", 132, Rarity.RARE, mage.cards.m.Malanthrope.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mandate of Abaddon", "40*", Rarity.RARE, mage.cards.m.MandateOfAbaddon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mandate of Abaddon", 40, Rarity.RARE, mage.cards.m.MandateOfAbaddon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Marneus Calgar", 175, Rarity.MYTHIC, mage.cards.m.MarneusCalgar.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Marneus Calgar", 8, Rarity.MYTHIC, mage.cards.m.MarneusCalgar.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Martial Coup", "189*", Rarity.RARE, mage.cards.m.MartialCoup.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Martial Coup", 189, Rarity.RARE, mage.cards.m.MartialCoup.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mask of Memory", "243*", Rarity.UNCOMMON, mage.cards.m.MaskOfMemory.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mask of Memory", 243, Rarity.UNCOMMON, mage.cards.m.MaskOfMemory.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mawloc", "133*", Rarity.RARE, mage.cards.m.Mawloc.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mawloc", 133, Rarity.RARE, mage.cards.m.Mawloc.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Memorial to Glory", "283*", Rarity.UNCOMMON, mage.cards.m.MemorialToGlory.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Memorial to Glory", 283, Rarity.UNCOMMON, mage.cards.m.MemorialToGlory.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mind Stone", "244*", Rarity.UNCOMMON, mage.cards.m.MindStone.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mind Stone", "245*", Rarity.UNCOMMON, mage.cards.m.MindStone.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mind Stone", 244, Rarity.UNCOMMON, mage.cards.m.MindStone.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mind Stone", 245, Rarity.UNCOMMON, mage.cards.m.MindStone.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Molten Slagheap", "284*", Rarity.UNCOMMON, mage.cards.m.MoltenSlagheap.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Molten Slagheap", 284, Rarity.UNCOMMON, mage.cards.m.MoltenSlagheap.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mortarion, Daemon Primarch", "41*", Rarity.RARE, mage.cards.m.MortarionDaemonPrimarch.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mortarion, Daemon Primarch", 41, Rarity.RARE, mage.cards.m.MortarionDaemonPrimarch.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mortify", "225*", Rarity.UNCOMMON, mage.cards.m.Mortify.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mortify", 225, Rarity.UNCOMMON, mage.cards.m.Mortify.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", "315*", Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", "316*", Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mountain", 315, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Mutalith Vortex Beast", 134, Rarity.RARE, mage.cards.m.MutalithVortexBeast.class)); - cards.add(new SetCardInfo("Mutilate", 203, Rarity.RARE, mage.cards.m.Mutilate.class)); - cards.add(new SetCardInfo("Myriad Landscape", 285, Rarity.UNCOMMON, mage.cards.m.MyriadLandscape.class)); - cards.add(new SetCardInfo("Mystic Forge", 246, Rarity.RARE, mage.cards.m.MysticForge.class)); - cards.add(new SetCardInfo("Necron Deathmark", 42, Rarity.RARE, mage.cards.n.NecronDeathmark.class)); - cards.add(new SetCardInfo("Necron Monolith", 161, Rarity.RARE, mage.cards.n.NecronMonolith.class)); - cards.add(new SetCardInfo("Necron Overlord", 43, Rarity.RARE, mage.cards.n.NecronOverlord.class)); - cards.add(new SetCardInfo("New Horizons", 218, Rarity.COMMON, mage.cards.n.NewHorizons.class)); - cards.add(new SetCardInfo("Nexos", 95, Rarity.RARE, mage.cards.n.Nexos.class)); - cards.add(new SetCardInfo("Neyam Shai Murad", 135, Rarity.RARE, mage.cards.n.NeyamShaiMurad.class)); - cards.add(new SetCardInfo("Night Scythe", 162, Rarity.UNCOMMON, mage.cards.n.NightScythe.class)); - cards.add(new SetCardInfo("Noise Marine", 82, Rarity.UNCOMMON, mage.cards.n.NoiseMarine.class)); - cards.add(new SetCardInfo("Nurgle's Conscription", 44, Rarity.RARE, mage.cards.n.NurglesConscription.class)); - cards.add(new SetCardInfo("Nurgle's Rot", 45, Rarity.UNCOMMON, mage.cards.n.NurglesRot.class)); - cards.add(new SetCardInfo("Old One Eye", 96, Rarity.RARE, mage.cards.o.OldOneEye.class)); - cards.add(new SetCardInfo("Opal Palace", 286, Rarity.COMMON, mage.cards.o.OpalPalace.class)); - cards.add(new SetCardInfo("Out of the Tombs", 46, Rarity.RARE, mage.cards.o.OutOfTheTombs.class)); - cards.add(new SetCardInfo("Overgrowth", 219, Rarity.COMMON, mage.cards.o.Overgrowth.class)); - cards.add(new SetCardInfo("Past in Flames", 206, Rarity.MYTHIC, mage.cards.p.PastInFlames.class)); - cards.add(new SetCardInfo("Path of Ancestry", 287, Rarity.COMMON, mage.cards.p.PathOfAncestry.class)); - cards.add(new SetCardInfo("Pink Horror", 136, Rarity.RARE, mage.cards.p.PinkHorror.class)); - cards.add(new SetCardInfo("Plague Drone", 47, Rarity.RARE, mage.cards.p.PlagueDrone.class)); + cards.add(new SetCardInfo("Mountain", 316, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mutalith Vortex Beast", "134*", Rarity.RARE, mage.cards.m.MutalithVortexBeast.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mutalith Vortex Beast", 134, Rarity.RARE, mage.cards.m.MutalithVortexBeast.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mutilate", "203*", Rarity.RARE, mage.cards.m.Mutilate.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mutilate", 203, Rarity.RARE, mage.cards.m.Mutilate.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Myriad Landscape", "285*", Rarity.UNCOMMON, mage.cards.m.MyriadLandscape.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Myriad Landscape", 285, Rarity.UNCOMMON, mage.cards.m.MyriadLandscape.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mystic Forge", "246*", Rarity.RARE, mage.cards.m.MysticForge.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mystic Forge", 246, Rarity.RARE, mage.cards.m.MysticForge.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Necron Deathmark", "42*", Rarity.RARE, mage.cards.n.NecronDeathmark.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Necron Deathmark", 42, Rarity.RARE, mage.cards.n.NecronDeathmark.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Necron Monolith", "161*", Rarity.RARE, mage.cards.n.NecronMonolith.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Necron Monolith", 161, Rarity.RARE, mage.cards.n.NecronMonolith.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Necron Overlord", "43*", Rarity.RARE, mage.cards.n.NecronOverlord.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Necron Overlord", 43, Rarity.RARE, mage.cards.n.NecronOverlord.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("New Horizons", "218*", Rarity.COMMON, mage.cards.n.NewHorizons.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("New Horizons", 218, Rarity.COMMON, mage.cards.n.NewHorizons.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Nexos", "95*", Rarity.RARE, mage.cards.n.Nexos.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Nexos", 95, Rarity.RARE, mage.cards.n.Nexos.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Neyam Shai Murad", "135*", Rarity.RARE, mage.cards.n.NeyamShaiMurad.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Neyam Shai Murad", 135, Rarity.RARE, mage.cards.n.NeyamShaiMurad.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Night Scythe", "162*", Rarity.UNCOMMON, mage.cards.n.NightScythe.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Night Scythe", 162, Rarity.UNCOMMON, mage.cards.n.NightScythe.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Noise Marine", "82*", Rarity.UNCOMMON, mage.cards.n.NoiseMarine.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Noise Marine", 82, Rarity.UNCOMMON, mage.cards.n.NoiseMarine.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Nurgle's Conscription", "44*", Rarity.RARE, mage.cards.n.NurglesConscription.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Nurgle's Conscription", 44, Rarity.RARE, mage.cards.n.NurglesConscription.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Nurgle's Rot", "45*", Rarity.UNCOMMON, mage.cards.n.NurglesRot.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Nurgle's Rot", 45, Rarity.UNCOMMON, mage.cards.n.NurglesRot.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Old One Eye", "96*", Rarity.RARE, mage.cards.o.OldOneEye.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Old One Eye", 96, Rarity.RARE, mage.cards.o.OldOneEye.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Opal Palace", "286*", Rarity.COMMON, mage.cards.o.OpalPalace.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Opal Palace", 286, Rarity.COMMON, mage.cards.o.OpalPalace.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Out of the Tombs", "46*", Rarity.RARE, mage.cards.o.OutOfTheTombs.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Out of the Tombs", 46, Rarity.RARE, mage.cards.o.OutOfTheTombs.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Overgrowth", "219*", Rarity.COMMON, mage.cards.o.Overgrowth.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Overgrowth", 219, Rarity.COMMON, mage.cards.o.Overgrowth.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Past in Flames", "206*", Rarity.MYTHIC, mage.cards.p.PastInFlames.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Past in Flames", 206, Rarity.MYTHIC, mage.cards.p.PastInFlames.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Path of Ancestry", "287*", Rarity.COMMON, mage.cards.p.PathOfAncestry.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Path of Ancestry", 287, Rarity.COMMON, mage.cards.p.PathOfAncestry.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Pink Horror", "136*", Rarity.RARE, mage.cards.p.PinkHorror.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Pink Horror", 136, Rarity.RARE, mage.cards.p.PinkHorror.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plague Drone", "47*", Rarity.RARE, mage.cards.p.PlagueDrone.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plague Drone", 47, Rarity.RARE, mage.cards.p.PlagueDrone.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", "306*", Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Plains", 306, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Plasmancer", 48, Rarity.UNCOMMON, mage.cards.p.Plasmancer.class)); - cards.add(new SetCardInfo("Polluted Mire", 288, Rarity.COMMON, mage.cards.p.PollutedMire.class)); - cards.add(new SetCardInfo("Port Town", 289, Rarity.RARE, mage.cards.p.PortTown.class)); - cards.add(new SetCardInfo("Poxwalkers", 49, Rarity.RARE, mage.cards.p.Poxwalkers.class)); - cards.add(new SetCardInfo("Prairie Stream", 290, Rarity.RARE, mage.cards.p.PrairieStream.class)); - cards.add(new SetCardInfo("Primaris Chaplain", 137, Rarity.UNCOMMON, mage.cards.p.PrimarisChaplain.class)); - cards.add(new SetCardInfo("Primaris Eliminator", 50, Rarity.RARE, mage.cards.p.PrimarisEliminator.class)); - cards.add(new SetCardInfo("Psychomancer", 51, Rarity.UNCOMMON, mage.cards.p.Psychomancer.class)); - cards.add(new SetCardInfo("Purestrain Genestealer", 97, Rarity.UNCOMMON, mage.cards.p.PurestrainGenestealer.class)); - cards.add(new SetCardInfo("Rampant Growth", 220, Rarity.COMMON, mage.cards.r.RampantGrowth.class)); - cards.add(new SetCardInfo("Ravener", 138, Rarity.RARE, mage.cards.r.Ravener.class)); - cards.add(new SetCardInfo("Reaver Titan", 163, Rarity.RARE, mage.cards.r.ReaverTitan.class)); - cards.add(new SetCardInfo("Reconnaissance Mission", 193, Rarity.UNCOMMON, mage.cards.r.ReconnaissanceMission.class)); - cards.add(new SetCardInfo("Redemptor Dreadnought", 164, Rarity.RARE, mage.cards.r.RedemptorDreadnought.class)); - cards.add(new SetCardInfo("Reliquary Tower", 291, Rarity.UNCOMMON, mage.cards.r.ReliquaryTower.class)); - cards.add(new SetCardInfo("Resurrection Orb", 165, Rarity.RARE, mage.cards.r.ResurrectionOrb.class)); - cards.add(new SetCardInfo("Reverberate", 207, Rarity.RARE, mage.cards.r.Reverberate.class)); - cards.add(new SetCardInfo("Royal Warden", 52, Rarity.RARE, mage.cards.r.RoyalWarden.class)); - cards.add(new SetCardInfo("Rugged Highlands", 292, Rarity.COMMON, mage.cards.r.RuggedHighlands.class)); - cards.add(new SetCardInfo("Sanguinary Priest", 53, Rarity.UNCOMMON, mage.cards.s.SanguinaryPriest.class)); - cards.add(new SetCardInfo("Sautekh Immortal", 54, Rarity.UNCOMMON, mage.cards.s.SautekhImmortal.class)); - cards.add(new SetCardInfo("Sceptre of Eternal Glory", 166, Rarity.RARE, mage.cards.s.SceptreOfEternalGlory.class)); - cards.add(new SetCardInfo("Scoured Barrens", 293, Rarity.COMMON, mage.cards.s.ScouredBarrens.class)); - cards.add(new SetCardInfo("Screamer-Killer", 84, Rarity.RARE, mage.cards.s.ScreamerKiller.class)); - cards.add(new SetCardInfo("Sculpting Steel", 247, Rarity.RARE, mage.cards.s.SculptingSteel.class)); - cards.add(new SetCardInfo("Seeker of Slaanesh", 85, Rarity.UNCOMMON, mage.cards.s.SeekerOfSlaanesh.class)); - cards.add(new SetCardInfo("Shadow in the Warp", 140, Rarity.RARE, mage.cards.s.ShadowInTheWarp.class)); - cards.add(new SetCardInfo("Shard of the Nightbringer", 55, Rarity.RARE, mage.cards.s.ShardOfTheNightbringer.class)); - cards.add(new SetCardInfo("Shard of the Void Dragon", 56, Rarity.RARE, mage.cards.s.ShardOfTheVoidDragon.class)); - cards.add(new SetCardInfo("Sicarian Infiltrator", 25, Rarity.UNCOMMON, mage.cards.s.SicarianInfiltrator.class)); - cards.add(new SetCardInfo("Sister Hospitaller", 141, Rarity.RARE, mage.cards.s.SisterHospitaller.class)); - cards.add(new SetCardInfo("Sister Repentia", 142, Rarity.RARE, mage.cards.s.SisterRepentia.class)); - cards.add(new SetCardInfo("Sister of Silence", 26, Rarity.RARE, mage.cards.s.SisterOfSilence.class)); - cards.add(new SetCardInfo("Skorpekh Destroyer", 57, Rarity.UNCOMMON, mage.cards.s.SkorpekhDestroyer.class)); - cards.add(new SetCardInfo("Skorpekh Lord", 58, Rarity.RARE, mage.cards.s.SkorpekhLord.class)); - cards.add(new SetCardInfo("Skullclamp", 248, Rarity.UNCOMMON, mage.cards.s.Skullclamp.class)); - cards.add(new SetCardInfo("Skycloud Expanse", 294, Rarity.RARE, mage.cards.s.SkycloudExpanse.class)); - cards.add(new SetCardInfo("Sloppity Bilepiper", 59, Rarity.RARE, mage.cards.s.SloppityBilepiper.class)); - cards.add(new SetCardInfo("Sol Ring", 249, Rarity.UNCOMMON, mage.cards.s.SolRing.class)); - cards.add(new SetCardInfo("Space Marine Devastator", 14, Rarity.RARE, mage.cards.s.SpaceMarineDevastator.class)); - cards.add(new SetCardInfo("Space Marine Scout", 15, Rarity.UNCOMMON, mage.cards.s.SpaceMarineScout.class)); - cards.add(new SetCardInfo("Sporocyst", 98, Rarity.RARE, mage.cards.s.Sporocyst.class)); - cards.add(new SetCardInfo("Starstorm", 208, Rarity.RARE, mage.cards.s.Starstorm.class)); - cards.add(new SetCardInfo("Sunken Hollow", 295, Rarity.RARE, mage.cards.s.SunkenHollow.class)); + cards.add(new SetCardInfo("Plasmancer", "48*", Rarity.UNCOMMON, mage.cards.p.Plasmancer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plasmancer", 48, Rarity.UNCOMMON, mage.cards.p.Plasmancer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Polluted Mire", "288*", Rarity.COMMON, mage.cards.p.PollutedMire.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Polluted Mire", 288, Rarity.COMMON, mage.cards.p.PollutedMire.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Port Town", "289*", Rarity.RARE, mage.cards.p.PortTown.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Port Town", 289, Rarity.RARE, mage.cards.p.PortTown.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Poxwalkers", "49*", Rarity.RARE, mage.cards.p.Poxwalkers.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Poxwalkers", 49, Rarity.RARE, mage.cards.p.Poxwalkers.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Prairie Stream", "290*", Rarity.RARE, mage.cards.p.PrairieStream.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Prairie Stream", 290, Rarity.RARE, mage.cards.p.PrairieStream.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Primaris Chaplain", "137*", Rarity.UNCOMMON, mage.cards.p.PrimarisChaplain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Primaris Chaplain", 137, Rarity.UNCOMMON, mage.cards.p.PrimarisChaplain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Primaris Eliminator", "50*", Rarity.RARE, mage.cards.p.PrimarisEliminator.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Primaris Eliminator", 50, Rarity.RARE, mage.cards.p.PrimarisEliminator.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Psychomancer", "51*", Rarity.UNCOMMON, mage.cards.p.Psychomancer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Psychomancer", 51, Rarity.UNCOMMON, mage.cards.p.Psychomancer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Purestrain Genestealer", "97*", Rarity.UNCOMMON, mage.cards.p.PurestrainGenestealer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Purestrain Genestealer", 97, Rarity.UNCOMMON, mage.cards.p.PurestrainGenestealer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Rampant Growth", "220*", Rarity.COMMON, mage.cards.r.RampantGrowth.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Rampant Growth", 220, Rarity.COMMON, mage.cards.r.RampantGrowth.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ravener", "138*", Rarity.RARE, mage.cards.r.Ravener.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ravener", 138, Rarity.RARE, mage.cards.r.Ravener.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Reaver Titan", "163*", Rarity.RARE, mage.cards.r.ReaverTitan.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Reaver Titan", 163, Rarity.RARE, mage.cards.r.ReaverTitan.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Reconnaissance Mission", "193*", Rarity.UNCOMMON, mage.cards.r.ReconnaissanceMission.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Reconnaissance Mission", 193, Rarity.UNCOMMON, mage.cards.r.ReconnaissanceMission.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Redemptor Dreadnought", "164*", Rarity.RARE, mage.cards.r.RedemptorDreadnought.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Redemptor Dreadnought", 164, Rarity.RARE, mage.cards.r.RedemptorDreadnought.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Reliquary Tower", "291*", Rarity.UNCOMMON, mage.cards.r.ReliquaryTower.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Reliquary Tower", 291, Rarity.UNCOMMON, mage.cards.r.ReliquaryTower.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Resurrection Orb", "165*", Rarity.RARE, mage.cards.r.ResurrectionOrb.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Resurrection Orb", 165, Rarity.RARE, mage.cards.r.ResurrectionOrb.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Reverberate", "207*", Rarity.RARE, mage.cards.r.Reverberate.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Reverberate", 207, Rarity.RARE, mage.cards.r.Reverberate.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Royal Warden", "52*", Rarity.RARE, mage.cards.r.RoyalWarden.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Royal Warden", 52, Rarity.RARE, mage.cards.r.RoyalWarden.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Rugged Highlands", "292*", Rarity.COMMON, mage.cards.r.RuggedHighlands.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Rugged Highlands", 292, Rarity.COMMON, mage.cards.r.RuggedHighlands.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sanguinary Priest", "53*", Rarity.UNCOMMON, mage.cards.s.SanguinaryPriest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sanguinary Priest", 53, Rarity.UNCOMMON, mage.cards.s.SanguinaryPriest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sautekh Immortal", "54*", Rarity.UNCOMMON, mage.cards.s.SautekhImmortal.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sautekh Immortal", 54, Rarity.UNCOMMON, mage.cards.s.SautekhImmortal.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sceptre of Eternal Glory", "166*", Rarity.RARE, mage.cards.s.SceptreOfEternalGlory.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sceptre of Eternal Glory", 166, Rarity.RARE, mage.cards.s.SceptreOfEternalGlory.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Scoured Barrens", "293*", Rarity.COMMON, mage.cards.s.ScouredBarrens.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Scoured Barrens", 293, Rarity.COMMON, mage.cards.s.ScouredBarrens.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Screamer-Killer", "84*", Rarity.RARE, mage.cards.s.ScreamerKiller.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Screamer-Killer", 84, Rarity.RARE, mage.cards.s.ScreamerKiller.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sculpting Steel", "247*", Rarity.RARE, mage.cards.s.SculptingSteel.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sculpting Steel", 247, Rarity.RARE, mage.cards.s.SculptingSteel.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Seeker of Slaanesh", "85*", Rarity.UNCOMMON, mage.cards.s.SeekerOfSlaanesh.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Seeker of Slaanesh", 85, Rarity.UNCOMMON, mage.cards.s.SeekerOfSlaanesh.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Shadow in the Warp", "140*", Rarity.RARE, mage.cards.s.ShadowInTheWarp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Shadow in the Warp", 140, Rarity.RARE, mage.cards.s.ShadowInTheWarp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Shard of the Nightbringer", "55*", Rarity.RARE, mage.cards.s.ShardOfTheNightbringer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Shard of the Nightbringer", 55, Rarity.RARE, mage.cards.s.ShardOfTheNightbringer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Shard of the Void Dragon", "56*", Rarity.RARE, mage.cards.s.ShardOfTheVoidDragon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Shard of the Void Dragon", 56, Rarity.RARE, mage.cards.s.ShardOfTheVoidDragon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sicarian Infiltrator", "25*", Rarity.UNCOMMON, mage.cards.s.SicarianInfiltrator.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sicarian Infiltrator", 25, Rarity.UNCOMMON, mage.cards.s.SicarianInfiltrator.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sister Hospitaller", "141*", Rarity.RARE, mage.cards.s.SisterHospitaller.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sister Hospitaller", 141, Rarity.RARE, mage.cards.s.SisterHospitaller.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sister Repentia", "142*", Rarity.RARE, mage.cards.s.SisterRepentia.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sister Repentia", 142, Rarity.RARE, mage.cards.s.SisterRepentia.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sister of Silence", "26*", Rarity.RARE, mage.cards.s.SisterOfSilence.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sister of Silence", 26, Rarity.RARE, mage.cards.s.SisterOfSilence.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Skorpekh Destroyer", "57*", Rarity.UNCOMMON, mage.cards.s.SkorpekhDestroyer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Skorpekh Destroyer", 57, Rarity.UNCOMMON, mage.cards.s.SkorpekhDestroyer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Skorpekh Lord", "58*", Rarity.RARE, mage.cards.s.SkorpekhLord.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Skorpekh Lord", 58, Rarity.RARE, mage.cards.s.SkorpekhLord.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Skullclamp", "248*", Rarity.UNCOMMON, mage.cards.s.Skullclamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Skullclamp", 248, Rarity.UNCOMMON, mage.cards.s.Skullclamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Skycloud Expanse", "294*", Rarity.RARE, mage.cards.s.SkycloudExpanse.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Skycloud Expanse", 294, Rarity.RARE, mage.cards.s.SkycloudExpanse.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sloppity Bilepiper", "59*", Rarity.RARE, mage.cards.s.SloppityBilepiper.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sloppity Bilepiper", 59, Rarity.RARE, mage.cards.s.SloppityBilepiper.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sol Ring", "249*", Rarity.UNCOMMON, mage.cards.s.SolRing.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sol Ring", "250*", Rarity.UNCOMMON, mage.cards.s.SolRing.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sol Ring", "251*", Rarity.UNCOMMON, mage.cards.s.SolRing.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sol Ring", "252*", Rarity.UNCOMMON, mage.cards.s.SolRing.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sol Ring", 249, Rarity.UNCOMMON, mage.cards.s.SolRing.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sol Ring", 250, Rarity.UNCOMMON, mage.cards.s.SolRing.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sol Ring", 251, Rarity.UNCOMMON, mage.cards.s.SolRing.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sol Ring", 252, Rarity.UNCOMMON, mage.cards.s.SolRing.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Space Marine Devastator", "14*", Rarity.RARE, mage.cards.s.SpaceMarineDevastator.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Space Marine Devastator", 14, Rarity.RARE, mage.cards.s.SpaceMarineDevastator.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Space Marine Scout", "15*", Rarity.UNCOMMON, mage.cards.s.SpaceMarineScout.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Space Marine Scout", 15, Rarity.UNCOMMON, mage.cards.s.SpaceMarineScout.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sporocyst", "98*", Rarity.RARE, mage.cards.s.Sporocyst.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sporocyst", 98, Rarity.RARE, mage.cards.s.Sporocyst.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Starstorm", "208*", Rarity.RARE, mage.cards.s.Starstorm.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Starstorm", 208, Rarity.RARE, mage.cards.s.Starstorm.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sunken Hollow", "295*", Rarity.RARE, mage.cards.s.SunkenHollow.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sunken Hollow", 295, Rarity.RARE, mage.cards.s.SunkenHollow.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swamp", "310*", Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swamp", "311*", Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swamp", "312*", Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swamp", "313*", Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swamp", "314*", Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Swamp", 310, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Swiftwater Cliffs", 296, Rarity.COMMON, mage.cards.s.SwiftwaterCliffs.class)); - cards.add(new SetCardInfo("Swords to Plowshares", 190, Rarity.UNCOMMON, mage.cards.s.SwordsToPlowshares.class)); - cards.add(new SetCardInfo("Szarekh, the Silent King", 1, Rarity.MYTHIC, mage.cards.s.SzarekhTheSilentKing.class)); - cards.add(new SetCardInfo("Talisman of Creativity", 253, Rarity.UNCOMMON, mage.cards.t.TalismanOfCreativity.class)); - cards.add(new SetCardInfo("Talisman of Dominance", 254, Rarity.UNCOMMON, mage.cards.t.TalismanOfDominance.class)); - cards.add(new SetCardInfo("Talisman of Hierarchy", 256, Rarity.UNCOMMON, mage.cards.t.TalismanOfHierarchy.class)); - cards.add(new SetCardInfo("Talisman of Indulgence", 257, Rarity.UNCOMMON, mage.cards.t.TalismanOfIndulgence.class)); - cards.add(new SetCardInfo("Talisman of Progress", 258, Rarity.UNCOMMON, mage.cards.t.TalismanOfProgress.class)); - cards.add(new SetCardInfo("Tallyman of Nurgle", 60, Rarity.RARE, mage.cards.t.TallymanOfNurgle.class)); - cards.add(new SetCardInfo("Technomancer", 61, Rarity.RARE, mage.cards.t.Technomancer.class)); - cards.add(new SetCardInfo("Temple of Abandon", 297, Rarity.RARE, mage.cards.t.TempleOfAbandon.class)); - cards.add(new SetCardInfo("Temple of Epiphany", 298, Rarity.RARE, mage.cards.t.TempleOfEpiphany.class)); - cards.add(new SetCardInfo("Temple of Mystery", 299, Rarity.RARE, mage.cards.t.TempleOfMystery.class)); - cards.add(new SetCardInfo("Temple of the False God", 300, Rarity.UNCOMMON, mage.cards.t.TempleOfTheFalseGod.class)); - cards.add(new SetCardInfo("Termagant Swarm", 99, Rarity.RARE, mage.cards.t.TermagantSwarm.class)); - cards.add(new SetCardInfo("Terramorphic Expanse", 301, Rarity.COMMON, mage.cards.t.TerramorphicExpanse.class)); - cards.add(new SetCardInfo("Tervigon", 100, Rarity.RARE, mage.cards.t.Tervigon.class)); - cards.add(new SetCardInfo("The First Tyrannic War", 121, Rarity.RARE, mage.cards.t.TheFirstTyrannicWar.class)); - cards.add(new SetCardInfo("The Flesh Is Weak", 122, Rarity.RARE, mage.cards.t.TheFleshIsWeak.class)); - cards.add(new SetCardInfo("The Golden Throne", 157, Rarity.RARE, mage.cards.t.TheGoldenThrone.class)); - cards.add(new SetCardInfo("The Horus Heresy", 126, Rarity.RARE, mage.cards.t.TheHorusHeresy.class)); - cards.add(new SetCardInfo("The Lost and the Damned", 129, Rarity.UNCOMMON, mage.cards.t.TheLostAndTheDamned.class)); - cards.add(new SetCardInfo("The Ruinous Powers", 139, Rarity.RARE, mage.cards.t.TheRuinousPowers.class)); - cards.add(new SetCardInfo("The Swarmlord", 4, Rarity.MYTHIC, mage.cards.t.TheSwarmlord.class)); - cards.add(new SetCardInfo("The War in Heaven", 69, Rarity.RARE, mage.cards.t.TheWarInHeaven.class)); - cards.add(new SetCardInfo("Their Name Is Death", 62, Rarity.RARE, mage.cards.t.TheirNameIsDeath.class)); - cards.add(new SetCardInfo("Their Number Is Legion", 63, Rarity.RARE, mage.cards.t.TheirNumberIsLegion.class)); - cards.add(new SetCardInfo("Thornwood Falls", 302, Rarity.COMMON, mage.cards.t.ThornwoodFalls.class)); - cards.add(new SetCardInfo("Thought Vessel", 259, Rarity.COMMON, mage.cards.t.ThoughtVessel.class)); - cards.add(new SetCardInfo("Thunderhawk Gunship", 167, Rarity.RARE, mage.cards.t.ThunderhawkGunship.class)); - cards.add(new SetCardInfo("Thunderwolf Cavalry", 16, Rarity.UNCOMMON, mage.cards.t.ThunderwolfCavalry.class)); - cards.add(new SetCardInfo("Tomb Blade", 64, Rarity.RARE, mage.cards.t.TombBlade.class)); - cards.add(new SetCardInfo("Tomb Fortress", 168, Rarity.RARE, mage.cards.t.TombFortress.class)); - cards.add(new SetCardInfo("Toxicrene", 101, Rarity.RARE, mage.cards.t.Toxicrene.class)); - cards.add(new SetCardInfo("Tranquil Cove", 303, Rarity.COMMON, mage.cards.t.TranquilCove.class)); - cards.add(new SetCardInfo("Trazyn the Infinite", 65, Rarity.RARE, mage.cards.t.TrazynTheInfinite.class)); - cards.add(new SetCardInfo("Triarch Praetorian", 66, Rarity.UNCOMMON, mage.cards.t.TriarchPraetorian.class)); - cards.add(new SetCardInfo("Triarch Stalker", 67, Rarity.RARE, mage.cards.t.TriarchStalker.class)); - cards.add(new SetCardInfo("Triumph of Saint Katherine", 17, Rarity.RARE, mage.cards.t.TriumphOfSaintKatherine.class)); - cards.add(new SetCardInfo("Trygon Prime", 143, Rarity.UNCOMMON, mage.cards.t.TrygonPrime.class)); - cards.add(new SetCardInfo("Tyranid Harridan", 144, Rarity.RARE, mage.cards.t.TyranidHarridan.class)); - cards.add(new SetCardInfo("Tyranid Invasion", 102, Rarity.UNCOMMON, mage.cards.t.TyranidInvasion.class)); - cards.add(new SetCardInfo("Tyranid Prime", 145, Rarity.RARE, mage.cards.t.TyranidPrime.class)); - cards.add(new SetCardInfo("Tyrant Guard", 103, Rarity.RARE, mage.cards.t.TyrantGuard.class)); - cards.add(new SetCardInfo("Tzaangor Shaman", 146, Rarity.RARE, mage.cards.t.TzaangorShaman.class)); - cards.add(new SetCardInfo("Ultramarines Honour Guard", 18, Rarity.RARE, mage.cards.u.UltramarinesHonourGuard.class)); - cards.add(new SetCardInfo("Unclaimed Territory", 304, Rarity.UNCOMMON, mage.cards.u.UnclaimedTerritory.class)); - cards.add(new SetCardInfo("Unstable Obelisk", 260, Rarity.COMMON, mage.cards.u.UnstableObelisk.class)); - cards.add(new SetCardInfo("Utter End", 226, Rarity.RARE, mage.cards.u.UtterEnd.class)); - cards.add(new SetCardInfo("Vanguard Suppressor", 27, Rarity.RARE, mage.cards.v.VanguardSuppressor.class)); - cards.add(new SetCardInfo("Vault of Whispers", 305, Rarity.COMMON, mage.cards.v.VaultOfWhispers.class)); - cards.add(new SetCardInfo("Venomcrawler", 68, Rarity.RARE, mage.cards.v.Venomcrawler.class)); - cards.add(new SetCardInfo("Venomthrope", 147, Rarity.UNCOMMON, mage.cards.v.Venomthrope.class)); - cards.add(new SetCardInfo("Vexilus Praetor", 19, Rarity.RARE, mage.cards.v.VexilusPraetor.class)); - cards.add(new SetCardInfo("Warstorm Surge", 209, Rarity.RARE, mage.cards.w.WarstormSurge.class)); - cards.add(new SetCardInfo("Wayfarer's Bauble", 261, Rarity.COMMON, mage.cards.w.WayfarersBauble.class)); - cards.add(new SetCardInfo("Winged Hive Tyrant", 148, Rarity.RARE, mage.cards.w.WingedHiveTyrant.class)); - cards.add(new SetCardInfo("Worn Powerstone", 263, Rarity.UNCOMMON, mage.cards.w.WornPowerstone.class)); - cards.add(new SetCardInfo("Zephyrim", 20, Rarity.RARE, mage.cards.z.Zephyrim.class)); - cards.add(new SetCardInfo("Zoanthrope", 149, Rarity.RARE, mage.cards.z.Zoanthrope.class)); + cards.add(new SetCardInfo("Swamp", 311, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swamp", 312, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swamp", 313, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swamp", 314, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swiftwater Cliffs", "296*", Rarity.COMMON, mage.cards.s.SwiftwaterCliffs.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swiftwater Cliffs", 296, Rarity.COMMON, mage.cards.s.SwiftwaterCliffs.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swords to Plowshares", "190*", Rarity.UNCOMMON, mage.cards.s.SwordsToPlowshares.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swords to Plowshares", 190, Rarity.UNCOMMON, mage.cards.s.SwordsToPlowshares.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Szarekh, the Silent King", 1, Rarity.MYTHIC, mage.cards.s.SzarekhTheSilentKing.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Szarekh, the Silent King", 170, Rarity.MYTHIC, mage.cards.s.SzarekhTheSilentKing.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Szarekh, the Silent King", 177, Rarity.MYTHIC, mage.cards.s.SzarekhTheSilentKing.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Szarekh, the Silent King", 318, Rarity.MYTHIC, mage.cards.s.SzarekhTheSilentKing.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Talisman of Creativity", "253*", Rarity.UNCOMMON, mage.cards.t.TalismanOfCreativity.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Talisman of Creativity", 253, Rarity.UNCOMMON, mage.cards.t.TalismanOfCreativity.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Talisman of Dominance", "254*", Rarity.UNCOMMON, mage.cards.t.TalismanOfDominance.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Talisman of Dominance", "255*", Rarity.UNCOMMON, mage.cards.t.TalismanOfDominance.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Talisman of Dominance", 254, Rarity.UNCOMMON, mage.cards.t.TalismanOfDominance.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Talisman of Dominance", 255, Rarity.UNCOMMON, mage.cards.t.TalismanOfDominance.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Talisman of Hierarchy", "256*", Rarity.UNCOMMON, mage.cards.t.TalismanOfHierarchy.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Talisman of Hierarchy", 256, Rarity.UNCOMMON, mage.cards.t.TalismanOfHierarchy.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Talisman of Indulgence", "257*", Rarity.UNCOMMON, mage.cards.t.TalismanOfIndulgence.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Talisman of Indulgence", 257, Rarity.UNCOMMON, mage.cards.t.TalismanOfIndulgence.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Talisman of Progress", "258*", Rarity.UNCOMMON, mage.cards.t.TalismanOfProgress.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Talisman of Progress", 258, Rarity.UNCOMMON, mage.cards.t.TalismanOfProgress.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tallyman of Nurgle", "60*", Rarity.RARE, mage.cards.t.TallymanOfNurgle.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tallyman of Nurgle", 60, Rarity.RARE, mage.cards.t.TallymanOfNurgle.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Technomancer", "61*", Rarity.RARE, mage.cards.t.Technomancer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Technomancer", 61, Rarity.RARE, mage.cards.t.Technomancer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Temple of Abandon", "297*", Rarity.RARE, mage.cards.t.TempleOfAbandon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Temple of Abandon", 297, Rarity.RARE, mage.cards.t.TempleOfAbandon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Temple of Epiphany", "298*", Rarity.RARE, mage.cards.t.TempleOfEpiphany.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Temple of Epiphany", 298, Rarity.RARE, mage.cards.t.TempleOfEpiphany.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Temple of Mystery", "299*", Rarity.RARE, mage.cards.t.TempleOfMystery.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Temple of Mystery", 299, Rarity.RARE, mage.cards.t.TempleOfMystery.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Temple of the False God", "300*", Rarity.UNCOMMON, mage.cards.t.TempleOfTheFalseGod.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Temple of the False God", 300, Rarity.UNCOMMON, mage.cards.t.TempleOfTheFalseGod.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Termagant Swarm", "99*", Rarity.RARE, mage.cards.t.TermagantSwarm.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Termagant Swarm", 99, Rarity.RARE, mage.cards.t.TermagantSwarm.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Terramorphic Expanse", "301*", Rarity.COMMON, mage.cards.t.TerramorphicExpanse.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Terramorphic Expanse", 301, Rarity.COMMON, mage.cards.t.TerramorphicExpanse.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tervigon", "100*", Rarity.RARE, mage.cards.t.Tervigon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tervigon", 100, Rarity.RARE, mage.cards.t.Tervigon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The First Tyrannic War", "121*", Rarity.RARE, mage.cards.t.TheFirstTyrannicWar.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The First Tyrannic War", 121, Rarity.RARE, mage.cards.t.TheFirstTyrannicWar.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Flesh Is Weak", "122*", Rarity.RARE, mage.cards.t.TheFleshIsWeak.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Flesh Is Weak", 122, Rarity.RARE, mage.cards.t.TheFleshIsWeak.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Golden Throne", "157*", Rarity.RARE, mage.cards.t.TheGoldenThrone.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Golden Throne", 157, Rarity.RARE, mage.cards.t.TheGoldenThrone.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Horus Heresy", "126*", Rarity.RARE, mage.cards.t.TheHorusHeresy.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Horus Heresy", 126, Rarity.RARE, mage.cards.t.TheHorusHeresy.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Lost and the Damned", "129*", Rarity.UNCOMMON, mage.cards.t.TheLostAndTheDamned.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Lost and the Damned", 129, Rarity.UNCOMMON, mage.cards.t.TheLostAndTheDamned.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Red Terror", "83*", Rarity.RARE, mage.cards.t.TheRedTerror.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Red Terror", 83, Rarity.RARE, mage.cards.t.TheRedTerror.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Ruinous Powers", "139*", Rarity.RARE, mage.cards.t.TheRuinousPowers.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Ruinous Powers", 139, Rarity.RARE, mage.cards.t.TheRuinousPowers.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Swarmlord", 176, Rarity.MYTHIC, mage.cards.t.TheSwarmlord.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Swarmlord", 180, Rarity.MYTHIC, mage.cards.t.TheSwarmlord.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Swarmlord", 321, Rarity.MYTHIC, mage.cards.t.TheSwarmlord.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Swarmlord", 4, Rarity.MYTHIC, mage.cards.t.TheSwarmlord.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The War in Heaven", "69*", Rarity.RARE, mage.cards.t.TheWarInHeaven.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The War in Heaven", 69, Rarity.RARE, mage.cards.t.TheWarInHeaven.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Their Name Is Death", "62*", Rarity.RARE, mage.cards.t.TheirNameIsDeath.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Their Name Is Death", 62, Rarity.RARE, mage.cards.t.TheirNameIsDeath.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Their Number Is Legion", "63*", Rarity.RARE, mage.cards.t.TheirNumberIsLegion.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Their Number Is Legion", 63, Rarity.RARE, mage.cards.t.TheirNumberIsLegion.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Thornwood Falls", "302*", Rarity.COMMON, mage.cards.t.ThornwoodFalls.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Thornwood Falls", 302, Rarity.COMMON, mage.cards.t.ThornwoodFalls.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Thought Vessel", "259*", Rarity.COMMON, mage.cards.t.ThoughtVessel.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Thought Vessel", 259, Rarity.COMMON, mage.cards.t.ThoughtVessel.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Thunderhawk Gunship", "167*", Rarity.RARE, mage.cards.t.ThunderhawkGunship.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Thunderhawk Gunship", 167, Rarity.RARE, mage.cards.t.ThunderhawkGunship.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Thunderwolf Cavalry", "16*", Rarity.UNCOMMON, mage.cards.t.ThunderwolfCavalry.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Thunderwolf Cavalry", 16, Rarity.UNCOMMON, mage.cards.t.ThunderwolfCavalry.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tomb Blade", "64*", Rarity.RARE, mage.cards.t.TombBlade.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tomb Blade", 64, Rarity.RARE, mage.cards.t.TombBlade.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tomb Fortress", "168*", Rarity.RARE, mage.cards.t.TombFortress.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tomb Fortress", 168, Rarity.RARE, mage.cards.t.TombFortress.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Toxicrene", "101*", Rarity.RARE, mage.cards.t.Toxicrene.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Toxicrene", 101, Rarity.RARE, mage.cards.t.Toxicrene.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tranquil Cove", "303*", Rarity.COMMON, mage.cards.t.TranquilCove.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tranquil Cove", 303, Rarity.COMMON, mage.cards.t.TranquilCove.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Trazyn the Infinite", "65*", Rarity.RARE, mage.cards.t.TrazynTheInfinite.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Trazyn the Infinite", 65, Rarity.RARE, mage.cards.t.TrazynTheInfinite.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Triarch Praetorian", "66*", Rarity.UNCOMMON, mage.cards.t.TriarchPraetorian.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Triarch Praetorian", 66, Rarity.UNCOMMON, mage.cards.t.TriarchPraetorian.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Triarch Stalker", "67*", Rarity.RARE, mage.cards.t.TriarchStalker.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Triarch Stalker", 67, Rarity.RARE, mage.cards.t.TriarchStalker.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Triumph of Saint Katherine", "17*", Rarity.RARE, mage.cards.t.TriumphOfSaintKatherine.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Triumph of Saint Katherine", 17, Rarity.RARE, mage.cards.t.TriumphOfSaintKatherine.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Trygon Prime", "143*", Rarity.UNCOMMON, mage.cards.t.TrygonPrime.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Trygon Prime", 143, Rarity.UNCOMMON, mage.cards.t.TrygonPrime.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tyranid Harridan", "144*", Rarity.RARE, mage.cards.t.TyranidHarridan.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tyranid Harridan", 144, Rarity.RARE, mage.cards.t.TyranidHarridan.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tyranid Invasion", "102*", Rarity.UNCOMMON, mage.cards.t.TyranidInvasion.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tyranid Invasion", 102, Rarity.UNCOMMON, mage.cards.t.TyranidInvasion.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tyranid Prime", "145*", Rarity.RARE, mage.cards.t.TyranidPrime.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tyranid Prime", 145, Rarity.RARE, mage.cards.t.TyranidPrime.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tyrant Guard", "103*", Rarity.RARE, mage.cards.t.TyrantGuard.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tyrant Guard", 103, Rarity.RARE, mage.cards.t.TyrantGuard.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tzaangor Shaman", "146*", Rarity.RARE, mage.cards.t.TzaangorShaman.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tzaangor Shaman", 146, Rarity.RARE, mage.cards.t.TzaangorShaman.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ultramarines Honour Guard", "18*", Rarity.RARE, mage.cards.u.UltramarinesHonourGuard.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ultramarines Honour Guard", 18, Rarity.RARE, mage.cards.u.UltramarinesHonourGuard.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Unclaimed Territory", "304*", Rarity.UNCOMMON, mage.cards.u.UnclaimedTerritory.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Unclaimed Territory", 304, Rarity.UNCOMMON, mage.cards.u.UnclaimedTerritory.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Unstable Obelisk", "260*", Rarity.COMMON, mage.cards.u.UnstableObelisk.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Unstable Obelisk", 260, Rarity.COMMON, mage.cards.u.UnstableObelisk.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Utter End", "226*", Rarity.RARE, mage.cards.u.UtterEnd.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Utter End", 226, Rarity.RARE, mage.cards.u.UtterEnd.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vanguard Suppressor", "27*", Rarity.RARE, mage.cards.v.VanguardSuppressor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vanguard Suppressor", 27, Rarity.RARE, mage.cards.v.VanguardSuppressor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vault of Whispers", "305*", Rarity.COMMON, mage.cards.v.VaultOfWhispers.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vault of Whispers", 305, Rarity.COMMON, mage.cards.v.VaultOfWhispers.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Venomcrawler", "68*", Rarity.RARE, mage.cards.v.Venomcrawler.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Venomcrawler", 68, Rarity.RARE, mage.cards.v.Venomcrawler.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Venomthrope", "147*", Rarity.UNCOMMON, mage.cards.v.Venomthrope.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Venomthrope", 147, Rarity.UNCOMMON, mage.cards.v.Venomthrope.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vexilus Praetor", "19*", Rarity.RARE, mage.cards.v.VexilusPraetor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vexilus Praetor", 19, Rarity.RARE, mage.cards.v.VexilusPraetor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Warstorm Surge", "209*", Rarity.RARE, mage.cards.w.WarstormSurge.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Warstorm Surge", 209, Rarity.RARE, mage.cards.w.WarstormSurge.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Wayfarer's Bauble", "261*", Rarity.COMMON, mage.cards.w.WayfarersBauble.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Wayfarer's Bauble", "262*", Rarity.COMMON, mage.cards.w.WayfarersBauble.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Wayfarer's Bauble", 261, Rarity.COMMON, mage.cards.w.WayfarersBauble.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Wayfarer's Bauble", 262, Rarity.COMMON, mage.cards.w.WayfarersBauble.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Winged Hive Tyrant", "148*", Rarity.RARE, mage.cards.w.WingedHiveTyrant.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Winged Hive Tyrant", 148, Rarity.RARE, mage.cards.w.WingedHiveTyrant.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Worn Powerstone", "263*", Rarity.UNCOMMON, mage.cards.w.WornPowerstone.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Worn Powerstone", 263, Rarity.UNCOMMON, mage.cards.w.WornPowerstone.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Zephyrim", "20*", Rarity.RARE, mage.cards.z.Zephyrim.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Zephyrim", 20, Rarity.RARE, mage.cards.z.Zephyrim.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Zoanthrope", "149*", Rarity.RARE, mage.cards.z.Zoanthrope.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Zoanthrope", 149, Rarity.RARE, mage.cards.z.Zoanthrope.class, NON_FULL_USE_VARIOUS)); } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/activated/LightningStormTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/activated/LightningStormTest.java index de5f27b9b06..8c06163c8d8 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/activated/LightningStormTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/activated/LightningStormTest.java @@ -33,9 +33,10 @@ public class LightningStormTest extends CardTestPlayerBase { // B discard and re-target activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Discard"); + setChoice(playerB, "Mountain"); // discard cost setChoice(playerB, true); // change target addTarget(playerB, playerA); // new target - setChoice(playerB, "Mountain"); // discard cost + // A discard and re-target activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Discard"); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/ProteanHydraTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/ProteanHydraTest.java index 17a56f1223a..e776a7cab04 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/ProteanHydraTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/ProteanHydraTest.java @@ -23,7 +23,7 @@ public class ProteanHydraTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Protean Hydra"); - setStrictChooseMode(false); // test AI use max for X + setStrictChooseMode(false); // TODO: good test for AI's announceX - duplicate it as AI test setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/ValakutTheMoltenPinnacleTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/ValakutTheMoltenPinnacleTest.java index 68dc852d72a..d9bea5640c2 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/ValakutTheMoltenPinnacleTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/ValakutTheMoltenPinnacleTest.java @@ -3,6 +3,7 @@ package org.mage.test.cards.abilities.enters; 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; /** @@ -61,18 +62,19 @@ public class ValakutTheMoltenPinnacleTest extends CardTestPlayerBase { @Test public void sixEnterWithScapeshiftDamageToPlayerB() { - addCard(Zone.LIBRARY, playerA, "Mountain", 6); + addCard(Zone.LIBRARY, playerA, "Mountain", 10); addCard(Zone.BATTLEFIELD, playerA, "Valakut, the Molten Pinnacle"); - addCard(Zone.BATTLEFIELD, playerA, "Forest", 6); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 10); addCard(Zone.HAND, playerA, "Scapeshift"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Scapeshift"); - setChoice(playerA, "Forest^Forest^Forest^Forest^Forest^Forest"); - addTarget(playerA, "Mountain^Mountain^Mountain^Mountain^Mountain^Mountain"); - setChoice(playerA, "Whenever", 5); // order triggers - setChoice(playerA, true, 6); // yes to deal damage + setChoice(playerA, "Forest^Forest^Forest^Forest^Forest^Forest"); // to sac + addTarget(playerA, "Mountain^Mountain^Mountain^Mountain^Mountain^Mountain"); // to search + setChoice(playerA, "Whenever a Mountain", 6 - 1); // x6 triggers from valakut addTarget(playerA, playerB, 6); // to deal damage + setChoice(playerA, true, 6); // yes to deal damage + setStrictChooseMode(true); setStopAt(3, PhaseStep.BEGIN_COMBAT); execute(); @@ -81,7 +83,7 @@ public class ValakutTheMoltenPinnacleTest extends CardTestPlayerBase { assertPermanentCount(playerA, "Mountain", 6); assertLife(playerA, 20); - assertLife(playerB, 2); // 6 * 3 damage = 18 + assertLife(playerB, 20 - 18); // 6 * 3 damage = 18 } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/flicker/CloudshiftTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/flicker/CloudshiftTest.java index e931e4a9235..9410a54d141 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/flicker/CloudshiftTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/flicker/CloudshiftTest.java @@ -178,7 +178,7 @@ public class CloudshiftTest extends CardTestPlayerBase { attack(3, playerA, "Silvercoat Lion"); - activateAbility(3, PhaseStep.END_COMBAT, playerA, "Remove a charge counter from {this}: Choose one —
&bull Equipped creature gets"); + activateAbility(3, PhaseStep.END_COMBAT, playerA, "Remove a charge counter from {this}: Choose one —
&bull Equipped creature gets"); setModeChoice(playerA, "1"); castSpell(3, PhaseStep.END_COMBAT, playerA, "Cloudshift", "Silvercoat Lion", "Remove a charge counter from"); @@ -215,7 +215,7 @@ public class CloudshiftTest extends CardTestPlayerBase { attack(3, playerA, "Silvercoat Lion"); - activateAbility(4, PhaseStep.DRAW, playerA, "Remove a charge counter from {this}: Choose one —
&bull Equipped creature gets"); + activateAbility(4, PhaseStep.DRAW, playerA, "Remove a charge counter from {this}: Choose one —
&bull Equipped creature gets"); setModeChoice(playerA, "1"); castSpell(4, PhaseStep.PRECOMBAT_MAIN, playerB, "Flickerwisp"); addTarget(playerB, "Silvercoat Lion"); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ImproviseTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ImproviseTest.java index 41510b0a393..e2e90a43af9 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ImproviseTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ImproviseTest.java @@ -3,6 +3,7 @@ package org.mage.test.cards.abilities.keywords; import mage.constants.PhaseStep; import mage.constants.Zone; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBaseWithAIHelps; /** @@ -26,9 +27,10 @@ public class ImproviseTest extends CardTestPlayerBaseWithAIHelps { activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {U}", 4); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bastion Inventor"); setChoice(playerA, "Blue", 4); // pay 1-4 - // improvise pay by one card + // improvise pay by one card (after 2025 addTarget improve - Improvise can be pay by multiple addTarget) setChoice(playerA, "Improvise"); addTarget(playerA, "Alpha Myr"); // pay 5 as improvise + addTarget(playerA, TestPlayer.TARGET_SKIP); // choose only 1 of 2 possible permanent setChoice(playerA, "Improvise"); addTarget(playerA, "Alpha Myr"); // pay 6 as improvise diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SaddleTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SaddleTest.java index 1a09b791aab..33051479103 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SaddleTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SaddleTest.java @@ -15,6 +15,11 @@ import org.mage.test.serverside.base.CardTestPlayerBase; */ public class SaddleTest extends CardTestPlayerBase { + /** + * Whenever Quilled Charger attacks while saddled, it gets +1/+2 and gains menace until end of turn. + *

+ * Saddle 2 + */ private static final String charger = "Quilled Charger"; private static final String bear = "Grizzly Bears"; @@ -47,29 +52,35 @@ public class SaddleTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, charger); addCard(Zone.BATTLEFIELD, playerA, bear); - setChoice(playerA, bear); + // turn 1 - saddle and trigger on attack activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Saddle"); - + setChoice(playerA, bear); attack(1, playerA, charger, playerB); + runCode("on saddle", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, (info, player, game) -> { + assertTapped(bear, true); + assertTapped(charger, true); + assertSaddled(charger, true); + assertAbility(playerA, charger, new MenaceAbility(false), true); + assertLife(playerB, 20 - 4 - 1); + }); setStrictChooseMode(true); - setStopAt(1, PhaseStep.END_TURN); - execute(); - - assertTapped(bear, true); - assertTapped(charger, true); - assertSaddled(charger, true); - assertAbility(playerA, charger, new MenaceAbility(false), true); - assertLife(playerB, 20 - 4 - 1); - setStopAt(2, PhaseStep.UPKEEP); execute(); + // turn 2 - saddle ends assertSaddled(charger, false); } + /** + * Whenever Rambling Possum attacks while saddled, it gains +1/+2 until end of turn. Then you may return any number + * of creatures that saddled it this turn to their owner's hand. + *

+ * Saddle 1 + */ private static final String possum = "Rambling Possum"; private static final String lion = "Silvercoat Lion"; + private static final String elf = "Arbor Elf"; @Test public void testSaddledThisTurn() { @@ -77,11 +88,11 @@ public class SaddleTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, bear); addCard(Zone.BATTLEFIELD, playerA, lion); - setChoice(playerA, bear); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Saddle"); + setChoice(playerA, bear); // to saddle cost attack(1, playerA, possum, playerB); - setChoice(playerA, bear); + setChoice(playerA, bear); // to return setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); @@ -100,19 +111,17 @@ public class SaddleTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, possum); addCard(Zone.BATTLEFIELD, playerA, bear); addCard(Zone.BATTLEFIELD, playerA, lion); + addCard(Zone.BATTLEFIELD, playerA, elf); - setChoice(playerA, bear); + // turn 1 - saddle x2 and trigger on attack activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Saddle"); - - setStrictChooseMode(true); - setStopAt(1, PhaseStep.PRECOMBAT_MAIN); - execute(); + setChoice(playerA, bear + "^" + lion); attack(1, playerA, possum, playerB); - setChoice(playerA, lion); + setChoice(playerA, elf); // to return (try to choose a wrong creature, so game must not allow to choose it) + setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); - // TODO: test framework must have tools to check targeting (as workaround try to check it by look at test command error) try { execute(); @@ -123,7 +132,8 @@ public class SaddleTest extends CardTestPlayerBase { } assertTapped(bear, true); - assertTapped(lion, false); + assertTapped(lion, true); + assertTapped(elf, false); assertTapped(possum, true); assertSaddled(possum, true); } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/damage/SoulBurnTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/damage/SoulBurnTest.java index 017d4715f69..f02ef2a7f41 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/damage/SoulBurnTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/damage/SoulBurnTest.java @@ -169,6 +169,7 @@ public class SoulBurnTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Soul Burn", "Craw Wurm"); + setStrictChooseMode(false); // TODO: good test for AI's announceX - duplicate it as AI test (few examples) setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); assertPermanentCount(playerB, "Craw Wurm", 1); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/exile/SearchNameExileTests.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/exile/SearchNameExileTests.java index 55b4465a293..71e83ef6543 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/exile/SearchNameExileTests.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/exile/SearchNameExileTests.java @@ -136,6 +136,8 @@ public class SearchNameExileTests extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "fused Ready // Willing"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Test of Talents", "Ready // Willing", "Ready // Willing"); + // TODO: a non strict cause a good AI choice test - make strict and duplicate as really AI test? + // in non strict mode AI must choose as much as possible in good "up to" target and half in bad target setStrictChooseMode(false); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/control/TakeControlWhileSearchingLibraryTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/control/TakeControlWhileSearchingLibraryTest.java index 39ee676487a..6abb3d2a9ac 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/control/TakeControlWhileSearchingLibraryTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/control/TakeControlWhileSearchingLibraryTest.java @@ -6,6 +6,7 @@ import mage.constants.PhaseStep; import mage.constants.Zone; import org.junit.Ignore; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; /** @@ -33,6 +34,7 @@ public class TakeControlWhileSearchingLibraryTest extends CardTestPlayerBase { activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {B}", 3); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Buried Alive"); addTarget(playerA, "Balduvian Bears"); + addTarget(playerA, TestPlayer.TARGET_SKIP); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); // after @@ -73,6 +75,7 @@ public class TakeControlWhileSearchingLibraryTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Buried Alive"); setChoice(playerB, true); // continue addTarget(playerB, "Balduvian Bears"); // player B must take control for searching + addTarget(playerB, TestPlayer.TARGET_SKIP); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); // after @@ -116,6 +119,7 @@ public class TakeControlWhileSearchingLibraryTest extends CardTestPlayerBase { setChoice(playerA, true); // yes, try to cast a creature card from lib setChoice(playerA, "Panglacial Wurm"); // try to cast addTarget(playerA, "Balduvian Bears"); // choice for searching + addTarget(playerA, TestPlayer.TARGET_SKIP); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); // after @@ -170,6 +174,7 @@ public class TakeControlWhileSearchingLibraryTest extends CardTestPlayerBase { setChoice(playerB, true); // yes, try to cast a creature card from lib setChoice(playerB, "Panglacial Wurm"); // try to cast addTarget(playerB, "Balduvian Bears"); // choice for searching + addTarget(playerB, TestPlayer.TARGET_SKIP); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); // after @@ -230,6 +235,7 @@ public class TakeControlWhileSearchingLibraryTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Buried Alive"); setChoice(playerB, true); // continue after new control addTarget(playerB, "Balduvian Bears"); + addTarget(playerB, TestPlayer.TARGET_SKIP); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); checkGraveyardCount("after grave a", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Balduvian Bears", 0); checkGraveyardCount("after grave b", 1, PhaseStep.PRECOMBAT_MAIN, playerB, "Balduvian Bears", 0); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/control/WillbreakerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/control/WillbreakerTest.java index 25116d90d8c..6fe3fd923c8 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/control/WillbreakerTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/control/WillbreakerTest.java @@ -35,7 +35,7 @@ public class WillbreakerTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion"); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{B}, Remove", "Silvercoat Lion"); - setChoice(playerA, "X=0"); + //setChoice(playerA, "X=0"); // auto-choose X=0 setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/AdjusterCostTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/AdjusterCostTest.java index 35c05e4c0ff..0ce9b91e4ec 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/AdjusterCostTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/AdjusterCostTest.java @@ -15,6 +15,7 @@ import mage.util.CardUtil; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBaseWithAIHelps; import java.util.Arrays; @@ -509,6 +510,7 @@ public class AdjusterCostTest extends CardTestPlayerBaseWithAIHelps { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Fireball"); setChoice(playerA, "X=2"); addTarget(playerA, "Arbor Elf^Arbor Elf"); + addTarget(playerA, TestPlayer.TARGET_SKIP); setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/CollectEvidenceTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/CollectEvidenceTest.java index 2e608ce4130..601610fc121 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/CollectEvidenceTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/CollectEvidenceTest.java @@ -10,6 +10,7 @@ import mage.counters.CounterType; import mage.game.stack.StackObject; import org.junit.Assert; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; /** @@ -280,6 +281,7 @@ public class CollectEvidenceTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, monitor); setChoice(playerA, true); setChoice(playerA, giant); + setChoice(playerA, TestPlayer.CHOICE_SKIP); setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/RemoveCounterCostTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/RemoveCounterCostTest.java index 41dd7262dbc..1d6ba250fcb 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/RemoveCounterCostTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/RemoveCounterCostTest.java @@ -3,6 +3,7 @@ package org.mage.test.cards.cost.additional; 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; /** @@ -22,6 +23,7 @@ public class RemoveCounterCostTest extends CardTestPlayerBase { activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}, Remove two +1/+1 counters"); setChoice(playerA, "Novijen Sages"); // counters to remove + setChoice(playerA, TestPlayer.CHOICE_SKIP); setChoice(playerA, "X=2"); // counters to remove setStrictChooseMode(true); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/adventure/AdventureCardsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/adventure/AdventureCardsTest.java index 473f80354d8..2197d57296d 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/cost/adventure/AdventureCardsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/adventure/AdventureCardsTest.java @@ -855,4 +855,57 @@ public class AdventureCardsTest extends CardTestPlayerBase { assertPermanentCount(playerA, "Servo Token", 3); assertExileCount(playerA, "Lonesome Unicorn", 1); } + + private static final String zanarkand = "Zanarkand, Ancient Metropolis"; + + @Test + public void test_ZanarkandRegularPlay() { + addCard(Zone.HAND, playerA, zanarkand); + + playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, zanarkand); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertTapped(zanarkand, true); + } + + private static final String fayth = "Lasting Fayth"; + private static final String heroToken = "Hero Token"; + + @Test + public void test_ZanarkandAdventure() { + addCard(Zone.HAND, playerA, zanarkand); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 6); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, fayth); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, heroToken, 1); + assertPowerToughness(playerA, heroToken, 1 + 6, 1 + 6); + assertExileCount(playerA, zanarkand, 1); + } + + @Test + public void test_ZanarkandAdventurePlusPlay() { + addCard(Zone.HAND, playerA, zanarkand); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 6); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, fayth); + + playLand(1, PhaseStep.POSTCOMBAT_MAIN, playerA, zanarkand); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, heroToken, 1); + assertPowerToughness(playerA, heroToken, 1 + 6, 1 + 6); + assertTapped(zanarkand, true); + assertExileCount(playerA, zanarkand, 0); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/ReadAheadTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/ReadAheadTest.java index b87cf4f45f8..72b486a8f63 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/ReadAheadTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/ReadAheadTest.java @@ -1,7 +1,9 @@ package org.mage.test.cards.enchantments; +import mage.abilities.keyword.ReadAheadAbility; import mage.constants.PhaseStep; import mage.constants.Zone; +import mage.counters.CounterType; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; @@ -69,4 +71,78 @@ public class ReadAheadTest extends CardTestPlayerBase { assertPermanentCount(playerA, war, 0); assertGraveyardCount(playerA, war, 1); } + + private static final String rite = "Rite of Belzenlok"; + private static final String babs = "Barbara Wright"; + + @Test + public void testBarbaraWrightChapter1() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4); + addCard(Zone.BATTLEFIELD, playerA, babs); + addCard(Zone.HAND, playerA, rite); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, rite); + setChoice(playerA, "X=1"); + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + assertCounterCount(rite, CounterType.LORE, 1); + assertPermanentCount(playerA, "Cleric Token", 2); + + setStopAt(3, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertCounterCount(rite, CounterType.LORE, 2); + assertPermanentCount(playerA, "Cleric Token", 2 + 2); + + setStopAt(5, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, rite, 1); + assertPermanentCount(playerA, rite, 0); + assertPermanentCount(playerA, "Cleric Token", 2 + 2); + assertPermanentCount(playerA, "Demon Token", 1); + } + + @Test + public void testBarbaraWrightChapter2() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4); + addCard(Zone.BATTLEFIELD, playerA, babs); + addCard(Zone.HAND, playerA, rite); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, rite); + setChoice(playerA, "X=2"); + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + assertCounterCount(rite, CounterType.LORE, 2); + assertAbility(playerA, rite, ReadAheadAbility.getInstance(), true); + assertPermanentCount(playerA, "Cleric Token", 2); + + setStopAt(3, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, rite, 1); + assertPermanentCount(playerA, rite, 0); + assertPermanentCount(playerA, "Cleric Token", 2); + assertPermanentCount(playerA, "Demon Token", 1); + } + + @Test + public void testBarbaraWrightChapter3() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4); + addCard(Zone.BATTLEFIELD, playerA, babs); + addCard(Zone.HAND, playerA, rite); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, rite); + setChoice(playerA, "X=3"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerA, rite, 1); + assertPermanentCount(playerA, rite, 0); + assertPermanentCount(playerA, "Cleric Token", 0); + assertPermanentCount(playerA, "Demon Token", 1); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/SagaTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/SagaTest.java index dccb20ec471..25d12b4ea0e 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/SagaTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/SagaTest.java @@ -35,14 +35,14 @@ public class SagaTest extends CardTestPlayerBase { execute(); assertCounterCount(rite, CounterType.LORE, 2); - assertPermanentCount(playerA, "Cleric Token", 4); + assertPermanentCount(playerA, "Cleric Token", 2 + 2); setStopAt(5, PhaseStep.BEGIN_COMBAT); execute(); assertGraveyardCount(playerA, rite, 1); assertPermanentCount(playerA, rite, 0); - assertPermanentCount(playerA, "Cleric Token", 4); + assertPermanentCount(playerA, "Cleric Token", 2 + 2); assertPermanentCount(playerA, "Demon Token", 1); } @@ -64,7 +64,7 @@ public class SagaTest extends CardTestPlayerBase { execute(); assertCounterCount(rite, CounterType.LORE, 2); - assertPermanentCount(playerA, "Cleric Token", 4); + assertPermanentCount(playerA, "Cleric Token", 2 + 2); castSpell(3, PhaseStep.POSTCOMBAT_MAIN, playerA, flicker, rite); setStopAt(3, PhaseStep.END_TURN); @@ -73,7 +73,7 @@ public class SagaTest extends CardTestPlayerBase { assertGraveyardCount(playerA, rite, 0); assertPermanentCount(playerA, rite, 1); assertCounterCount(playerA, rite, CounterType.LORE, 1); - assertPermanentCount(playerA, "Cleric Token", 6); + assertPermanentCount(playerA, "Cleric Token", 2 + 2 + 2); assertPermanentCount(playerA, "Demon Token", 0); } @@ -95,7 +95,7 @@ public class SagaTest extends CardTestPlayerBase { execute(); assertCounterCount(rite, CounterType.LORE, 2); - assertPermanentCount(playerA, "Cleric Token", 4); + assertPermanentCount(playerA, "Cleric Token", 2 + 2); castSpell(5, PhaseStep.PRECOMBAT_MAIN, playerA, boomerang, rite); setStopAt(5, PhaseStep.BEGIN_COMBAT); @@ -104,7 +104,7 @@ public class SagaTest extends CardTestPlayerBase { assertHandCount(playerA, rite, 1); assertPermanentCount(playerA, rite, 0); assertGraveyardCount(playerA, boomerang, 1); - assertPermanentCount(playerA, "Cleric Token", 4); + assertPermanentCount(playerA, "Cleric Token", 2 + 2); assertPermanentCount(playerA, "Demon Token", 1); } @@ -119,14 +119,14 @@ public class SagaTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.PRECOMBAT_MAIN); execute(); assertCounterCount(rite, CounterType.LORE, 2); - assertPermanentCount(playerA, "Cleric Token", 4); + assertPermanentCount(playerA, "Cleric Token", 2 + 2); setStopAt(3, PhaseStep.PRECOMBAT_MAIN); execute(); assertGraveyardCount(playerA, rite, 1); assertPermanentCount(playerA, rite, 0); - assertPermanentCount(playerA, "Cleric Token", 4); + assertPermanentCount(playerA, "Cleric Token", 2 + 2); assertPermanentCount(playerA, "Demon Token", 1); } @@ -220,14 +220,14 @@ public class SagaTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, triumph); addTarget(playerA, memnite); - checkPT("+1/+0", 1, PhaseStep.BEGIN_COMBAT, playerA, memnite, 2, 1); + checkPT("+1/+0", 1, PhaseStep.BEGIN_COMBAT, playerA, memnite, 1 + 1, 1); checkPT("next turn", 2, PhaseStep.BEGIN_COMBAT, playerA, memnite, 1, 1); addTarget(playerA, memnite); - checkPT("+2/+0", 3, PhaseStep.BEGIN_COMBAT, playerA, memnite, 3, 1); + checkPT("+2/+0", 3, PhaseStep.BEGIN_COMBAT, playerA, memnite, 1 + 1 + 1, 1); addTarget(playerA, memnite); - checkPT("+3/+0", 5, PhaseStep.BEGIN_COMBAT, playerA, memnite, 4, 1); + checkPT("+3/+0", 5, PhaseStep.BEGIN_COMBAT, playerA, memnite, 1 + 1 + 1 + 1, 1); addTarget(playerA, memnite); addTarget(playerA, kraken); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/mana/AddManaOfAnyTypeProducedTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/mana/AddManaOfAnyTypeProducedTest.java index db6ab097935..813b019f70f 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/mana/AddManaOfAnyTypeProducedTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/mana/AddManaOfAnyTypeProducedTest.java @@ -29,6 +29,7 @@ public class AddManaOfAnyTypeProducedTest extends CardTestPlayerBase { activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {U}"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Vedalken Mastermind"); + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); @@ -52,6 +53,7 @@ public class AddManaOfAnyTypeProducedTest extends CardTestPlayerBase { activateManaAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {U}"); + setStrictChooseMode(true); setStopAt(3, PhaseStep.BEGIN_COMBAT); execute(); @@ -71,19 +73,26 @@ public class AddManaOfAnyTypeProducedTest extends CardTestPlayerBase { // If Gemstone Caverns is in your opening hand and you're not playing first, you may begin the game with Gemstone Caverns on the battlefield with a luck counter on it. If you do, exile a card from your hand. // {T}: Add {C}. If Gemstone Caverns has a luck counter on it, instead add one mana of any color. addCard(Zone.HAND, playerB, "Gemstone Caverns", 1); + addCard(Zone.HAND, playerB, "Swamp", 1); addCard(Zone.HAND, playerB, "Silvercoat Lion", 2); + // pay and put Gemstone to battlefield on starting + setChoice(playerB, true); + setChoice(playerB, "Swamp"); + activateManaAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "{T}: Add"); setChoice(playerB, "White"); castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Silvercoat Lion"); + + setStrictChooseMode(true); setStopAt(2, PhaseStep.BEGIN_COMBAT); execute(); assertPermanentCount(playerB, "Gemstone Caverns", 1); assertCounterCount("Gemstone Caverns", CounterType.LUCK, 1); assertPermanentCount(playerB, "Silvercoat Lion", 1); - assertExileCount("Silvercoat Lion", 1); + assertExileCount("Swamp", 1); assertTapped("Gemstone Caverns", true); } @@ -99,12 +108,19 @@ public class AddManaOfAnyTypeProducedTest extends CardTestPlayerBase { // If Gemstone Caverns is in your opening hand and you're not playing first, you may begin the game with Gemstone Caverns on the battlefield with a luck counter on it. If you do, exile a card from your hand. // {T}: Add {C}. If Gemstone Caverns has a luck counter on it, instead add one mana of any color. addCard(Zone.HAND, playerB, "Gemstone Caverns", 1); + addCard(Zone.HAND, playerB, "Swamp", 1); addCard(Zone.HAND, playerB, "Silvercoat Lion", 2); - activateManaAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "{T}: Add"); + // pay and put Gemstone to battlefield on starting + setChoice(playerB, true); + setChoice(playerB, "Swamp"); + + activateManaAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "{T}: Add {C}"); setChoice(playerB, "White"); castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Vorinclex, Voice of Hunger"); + + setStrictChooseMode(true); setStopAt(2, PhaseStep.BEGIN_COMBAT); execute(); @@ -112,7 +128,6 @@ public class AddManaOfAnyTypeProducedTest extends CardTestPlayerBase { assertCounterCount("Gemstone Caverns", CounterType.LUCK, 1); assertPermanentCount(playerB, "Vorinclex, Voice of Hunger", 1); assertTapped("Gemstone Caverns", true); - } private static final String kinnan = "Kinnan, Bonder Prodigy"; diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/mana/LimitPerTurnTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/mana/LimitPerTurnTest.java new file mode 100644 index 00000000000..1b2e090c09e --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/mana/LimitPerTurnTest.java @@ -0,0 +1,72 @@ +package org.mage.test.cards.mana; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class LimitPerTurnTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.s.ShireScarecrow Shire Scarecrow} {2} + * Artifact Creature — Scarecrow + * Defender + * {1}: Add one mana of any color. Activate only once each turn. + * 0/3 + */ + private static final String scarecrow = "Shire Scarecrow"; + + + @Test + public void test_LimitOncePerTurn() { + String vanguard = "Elite Vanguard"; // {W} 2/1 + String goblin = "Raging Goblin"; // {R} 1/1 + addCard(Zone.BATTLEFIELD, playerA, scarecrow, 1); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); + addCard(Zone.HAND, playerA, vanguard, 1); + addCard(Zone.HAND, playerA, goblin, 1); + + checkPlayableAbility("1: Vanguard can be cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + vanguard, true); + checkPlayableAbility("1: goblin can be cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + goblin, true); + + setChoice(playerA, "White"); // choice for Scarecrow mana + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, vanguard); + + checkPlayableAbility("2: goblin can not be cast", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast " + goblin, false); + + checkPlayableAbility("3: goblin can be cast on turn 3", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + goblin, true); + + setChoice(playerA, "Red"); // choice for Scarecrow mana + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, goblin); + + setStopAt(3, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertPermanentCount(playerA, 5); + } + + @Test + public void test_MultipleScarecrows() { + String deus = "Deus of Calamity"; // {R/G}{R/G}{R/G}{R/G}{R/G} + String boggart = "Boggart Ram-Gang"; // {R/G}{R/G}{R/G} + addCard(Zone.BATTLEFIELD, playerA, scarecrow, 3); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 5); + addCard(Zone.HAND, playerA, deus, 1); + addCard(Zone.HAND, playerA, boggart, 1); + + checkPlayableAbility("1: boggart can be cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + boggart, true); + checkPlayableAbility("1: deus can not be cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + deus, false); + + setChoice(playerA, "Red"); // choice for Scarecrow mana + setChoice(playerA, "Red"); // choice for Scarecrow mana + setChoice(playerA, "Green"); // choice for Scarecrow mana + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, boggart); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertPermanentCount(playerA, boggart, 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/rules/MeliraSylvokOutcastTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/rules/MeliraSylvokOutcastTest.java index 9f7e32e4b0f..84ab8a6f86b 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/rules/MeliraSylvokOutcastTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/rules/MeliraSylvokOutcastTest.java @@ -35,7 +35,7 @@ public class MeliraSylvokOutcastTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.BEGIN_COMBAT); - // TODO: Needed since Melira's ability isn't been caught by the is playable check + // TODO: improve PutCountersSourceCost, so it can find real playable ability here instead restriction try { execute(); Assert.fail("must throw exception on execute"); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/_40k/LuciusTheEternalTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/_40k/LuciusTheEternalTest.java new file mode 100644 index 00000000000..3caf33eeb4b --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/_40k/LuciusTheEternalTest.java @@ -0,0 +1,108 @@ +package org.mage.test.cards.single._40k; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class LuciusTheEternalTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.l.LuciusTheEternal Lucius the Eternal} {3}{B}{R} + * Legendary Creature — Astartes Warrior + * Haste + * Armour of Shrieking Souls — When Lucius the Eternal dies, exile it and choose target creature an opponent controls. When that creature leaves the battlefield, return this card from exile to the battlefield under its owner’s control. + * 5/3 + */ + private static final String lucius = "Lucius the Eternal"; + + /** + * Go for the Throat {1}{B} Instant + * Destroy target nonartifact creature. + */ + private static final String goForTheThroat = "Go for the Throat"; + /** + * Unsummon {U} Instant + * Return target creature to its owner’s hand. + */ + private static final String unsummon = "Unsummon"; + /** + * 2/2 bear + */ + private static final String bear = "Grizzly Bears"; + + @Test + public void testLuciusDelayedOnDieTrigger() { + addCard(Zone.BATTLEFIELD, playerA, lucius, 1); + addCard(Zone.BATTLEFIELD, playerB, bear, 1); + addCard(Zone.HAND, playerA, goForTheThroat, 2); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, goForTheThroat, lucius); + addTarget(playerA, bear);// Lucius dies trigger + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + checkExileCount("Lucius in exile", 1, PhaseStep.PRECOMBAT_MAIN, playerA, lucius, 1); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, goForTheThroat, bear); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + checkPermanentCount("Lucius in play", 1, PhaseStep.PRECOMBAT_MAIN, playerA, lucius, 1); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertGraveyardCount(playerA, 2); + assertPermanentCount(playerA, lucius, 1); + assertGraveyardCount(playerB, 1); + } + + @Test + public void testLuciusDelayedOnReturnTrigger() { + addCard(Zone.BATTLEFIELD, playerA, lucius, 1); + addCard(Zone.BATTLEFIELD, playerB, bear, 1); + addCard(Zone.HAND, playerA, goForTheThroat, 1); + addCard(Zone.HAND, playerA, unsummon, 1); + addCard(Zone.BATTLEFIELD, playerA, "Underground Sea", 3); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, goForTheThroat, lucius); + addTarget(playerA, bear);// Lucius dies trigger + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + checkExileCount("Lucius in exile", 1, PhaseStep.PRECOMBAT_MAIN, playerA, lucius, 1); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, unsummon, bear); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + checkPermanentCount("Lucius in play", 1, PhaseStep.PRECOMBAT_MAIN, playerA, lucius, 1); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertGraveyardCount(playerA, 2); + assertPermanentCount(playerA, lucius, 1); + assertHandCount(playerB, bear, 1); + } + + @Test + public void testLuciusNeverReturn() { + addCard(Zone.BATTLEFIELD, playerA, lucius, 1); + addCard(Zone.BATTLEFIELD, playerB, bear, 1); + addCard(Zone.HAND, playerA, goForTheThroat, 1); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, goForTheThroat, lucius); + addTarget(playerA, bear);// Lucius dies trigger + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertGraveyardCount(playerA, 1); + assertExileCount(playerA, lucius, 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/_40k/TheRedTerrorTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/_40k/TheRedTerrorTest.java new file mode 100644 index 00000000000..01e2cac5cea --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/_40k/TheRedTerrorTest.java @@ -0,0 +1,254 @@ +package org.mage.test.cards.single._40k; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class TheRedTerrorTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.t.TheRedTerror The Red Terror} {3}{R} + * Legendary Creature — Tyranid + * Advanced Species — Whenever a red source you control deals damage to one or more permanents and/or players, put a +1/+1 counter on The Red Terror. + * 4/3 + */ + private static final String terror = "The Red Terror"; + + /** + * 2/1, notably non-red + */ + private static final String vanguard = "Elite Vanguard"; + /** + * 2/1, notably red + */ + private static final String piker = "Goblin Piker"; + /** + * 2/1, notably red + */ + private static final String earthElemental = "Earth Elemental"; + + /** + * {R} 3 damage to any target + */ + private static final String bolt = "Lightning Bolt"; + /** + * Rivals' Duel {3}{R} + * Sorcery + * Choose two target creatures that share no creature types. Those creatures fight each other. + */ + private static final String duel = "Rivals' Duel"; + /** + * {1}{R} 2 damage to each creature + */ + private static final String pyroclasm = "Pyroclasm"; + /** + * Fiery Confluence {2}{R}{R} + * Sorcery + * Choose three. You may choose the same mode more than once. + * • Fiery Confluence deals 1 damage to each creature. + * • Fiery Confluence deals 2 damage to each opponent. + * • Destroy target artifact. + */ + private static final String confluence = "Fiery Confluence"; + + @Test + public void testTriggerCombatSimple() { + addCard(Zone.BATTLEFIELD, playerA, terror, 1); + + attack(1, playerA, terror, playerB); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertCounterCount(playerA, terror, CounterType.P1P1, 1); + assertLife(playerB, 20 - 4); + } + + @Test + public void testNoTriggerCombatNonRed() { + addCard(Zone.BATTLEFIELD, playerA, terror, 1); + addCard(Zone.BATTLEFIELD, playerA, vanguard, 1); + + attack(1, playerA, vanguard, playerB); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertCounterCount(playerA, terror, CounterType.P1P1, 0); + assertLife(playerB, 20 - 2); + } + + @Test + public void testTriggerCombatRed() { + addCard(Zone.BATTLEFIELD, playerA, terror, 1); + addCard(Zone.BATTLEFIELD, playerA, earthElemental, 1); + + attack(1, playerA, earthElemental, playerB); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertCounterCount(playerA, terror, CounterType.P1P1, 1); + assertLife(playerB, 20 - 4); + } + + @Test + public void testDoubleTriggerCombatRed() { + addCard(Zone.BATTLEFIELD, playerA, terror, 1); + addCard(Zone.BATTLEFIELD, playerA, vanguard, 1); + addCard(Zone.BATTLEFIELD, playerA, earthElemental, 1); + + attack(1, playerA, terror, playerB); + attack(1, playerA, vanguard, playerB); + attack(1, playerA, earthElemental, playerB); + + setChoice(playerA, "Advanced Species"); // 2 triggers need ordering. + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertCounterCount(playerA, terror, CounterType.P1P1, 2); + assertLife(playerB, 20 - 4 - 2 - 4); + } + + @Test + public void testNoTriggerNonRedBlockedByRed() { + addCard(Zone.BATTLEFIELD, playerA, terror, 1); + addCard(Zone.BATTLEFIELD, playerA, vanguard, 1); + addCard(Zone.BATTLEFIELD, playerB, earthElemental, 1); + + attack(1, playerA, vanguard, playerB); + block(1, playerB, earthElemental, vanguard); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertCounterCount(playerA, terror, CounterType.P1P1, 0); + assertLife(playerB, 20); + assertGraveyardCount(playerA, vanguard, 1); + assertDamageReceived(playerB, earthElemental, 2); + } + + @Test + public void testTriggerRedBlockedByRed() { + addCard(Zone.BATTLEFIELD, playerA, terror, 1); + addCard(Zone.BATTLEFIELD, playerA, earthElemental, 1); + addCard(Zone.BATTLEFIELD, playerB, earthElemental, 1); + + attack(1, playerA, earthElemental, playerB); + block(1, playerB, earthElemental, earthElemental); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertCounterCount(playerA, terror, CounterType.P1P1, 1); + assertLife(playerB, 20); + assertDamageReceived(playerA, earthElemental, 4); + assertDamageReceived(playerB, earthElemental, 4); + } + + @Test + public void testTriggerPyroclasm() { + addCard(Zone.HAND, playerA, pyroclasm, 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.BATTLEFIELD, playerA, terror, 1); + addCard(Zone.BATTLEFIELD, playerA, earthElemental, 1); + addCard(Zone.BATTLEFIELD, playerA, vanguard, 1); + addCard(Zone.BATTLEFIELD, playerB, earthElemental, 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, pyroclasm); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertCounterCount(playerA, terror, CounterType.P1P1, 1); + assertDamageReceived(playerA, earthElemental, 2); + assertDamageReceived(playerA, terror, 2); + assertGraveyardCount(playerA, vanguard, 1); + assertDamageReceived(playerB, earthElemental, 2); + } + + @Test + public void testTriggerBoltFace() { + addCard(Zone.HAND, playerA, bolt, 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + addCard(Zone.BATTLEFIELD, playerA, terror, 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bolt, playerB); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertCounterCount(playerA, terror, CounterType.P1P1, 1); + assertLife(playerB, 20 - 3); + } + + @Test + public void testTriggerBoltCreature() { + addCard(Zone.HAND, playerA, bolt, 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + addCard(Zone.BATTLEFIELD, playerA, terror, 1); + addCard(Zone.BATTLEFIELD, playerA, earthElemental, 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bolt, earthElemental); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertCounterCount(playerA, terror, CounterType.P1P1, 1); + assertDamageReceived(playerA, earthElemental, 3); + } + + @Test + public void testDoubleTriggerDuelTwoRed() { + addCard(Zone.HAND, playerA, duel, 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); + addCard(Zone.BATTLEFIELD, playerA, terror, 1); + addCard(Zone.BATTLEFIELD, playerA, piker, 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, duel, terror + "^" + piker); + + setChoice(playerA, "Advanced Species"); // 2 triggers need ordering. + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertCounterCount(playerA, terror, CounterType.P1P1, 2); + } + + @Test + public void testTripleTriggerFieryConfluence() { + addCard(Zone.HAND, playerA, confluence, 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); + addCard(Zone.BATTLEFIELD, playerA, terror, 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, confluence); + setModeChoice(playerA, "2"); // Fiery Confluence deals 2 damage to each opponent. + setModeChoice(playerA, "2"); // Fiery Confluence deals 2 damage to each opponent. + setModeChoice(playerA, "2"); // Fiery Confluence deals 2 damage to each opponent. + + setChoice(playerA, "Advanced Species", 2); // 3 triggers need ordering. + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertCounterCount(playerA, terror, CounterType.P1P1, 3); + assertLife(playerB, 20 - 2 * 3); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/acr/SigurdJarlOfRavensthorpeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/acr/SigurdJarlOfRavensthorpeTest.java new file mode 100644 index 00000000000..2dd6b577e64 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/acr/SigurdJarlOfRavensthorpeTest.java @@ -0,0 +1,54 @@ +package org.mage.test.cards.single.acr; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class SigurdJarlOfRavensthorpeTest extends CardTestPlayerBase { + + /** + * Whenever you put a lore counter on a Saga you control, put a +1/+1 counter on up to one other target creature. + * Boast -- {1}: Put a lore counter on target Saga you control or remove one from it. + */ + private static final String sigurd = "Sigurd, Jarl of Ravensthorpe"; + /** + * Saga land + */ + private static final String saga = "Urza's Saga"; + /** + * 2/2 Green Creature + */ + private static final String bear = "Bear Cub"; + + @Test + public void testSigurd() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, sigurd); + addCard(Zone.BATTLEFIELD, playerA, bear); + addCard(Zone.HAND, playerA, saga); + addCard(Zone.BATTLEFIELD, playerA, "Forest"); + + // Saga entering triggers + playLand(1, PhaseStep.PRECOMBAT_MAIN,playerA, saga); + addTarget(playerA, bear); + setChoice(playerA, "I - {this} gains"); // Stack Sigurd ability last + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCounters("Saga entering counter", 1, PhaseStep.PRECOMBAT_MAIN, playerA, saga, CounterType.LORE, 1); + checkPermanentCounters("Bear cub single counter", 1, PhaseStep.PRECOMBAT_MAIN, playerA, bear, CounterType.P1P1, 1); + + // Activate boast + attack(1, playerA, sigurd); + activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Boast — {1}: ", saga); + setChoice(playerA, true); // add counter + setChoice(playerA, "II - {this} gains"); // Stack Sigurd ability last + addTarget(playerA, bear); + waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN); + checkPermanentCounters("Saga boast counter", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, saga, CounterType.LORE, 2); + checkPermanentCounters("Bear cub two counters", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, bear, CounterType.P1P1, 2); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/afc/ShareTheSpoilsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/afc/ShareTheSpoilsTest.java index 8c6ab794249..844ca5cfb0a 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/afc/ShareTheSpoilsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/afc/ShareTheSpoilsTest.java @@ -3,6 +3,7 @@ package org.mage.test.cards.single.afc; import mage.constants.PhaseStep; import mage.constants.Zone; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestCommander4Players; @@ -313,6 +314,7 @@ public class ShareTheSpoilsTest extends CardTestCommander4Players { // Cast an adventure card from hand castSpell(5, PhaseStep.PRECOMBAT_MAIN, playerA, "Dizzying Swoop"); addTarget(playerA, "Prosper, Tome-Bound"); + addTarget(playerA, TestPlayer.TARGET_SKIP); // tap 1 of 2 targets waitStackResolved(5, PhaseStep.PRECOMBAT_MAIN); // Make sure the creature card can't be played from exile since there isn't the {W}{W} for it diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/akh/ApproachOfTheSecondSunTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/akh/ApproachOfTheSecondSunTest.java index 7e06886f60a..bbc8a10ac93 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/akh/ApproachOfTheSecondSunTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/akh/ApproachOfTheSecondSunTest.java @@ -169,15 +169,25 @@ public class ApproachOfTheSecondSunTest extends CardTestPlayerBase { @Test public void testCastFromGraveyard() { removeAllCardsFromLibrary(playerA); + addCard(Zone.LIBRARY, playerA, "Plains", 6); - addCard(Zone.HAND, playerA, "Approach of the Second Sun", 1); - addCard(Zone.GRAVEYARD, playerA, "Approach of the Second Sun", 2); - addCard(Zone.HAND, playerA, "Finale of Promise", 2); - addCard(Zone.BATTLEFIELD, playerA, "Mystic Monastery", 25); + // + // If this spell was cast from your hand and you've cast another spell named Approach of the Second Sun this game, + // you win the game. Otherwise, put Approach of the Second Sun into its owner's library seventh from the top + // and you gain 7 life. + addCard(Zone.HAND, playerA, "Approach of the Second Sun", 1); // {6}{W} + addCard(Zone.GRAVEYARD, playerA, "Approach of the Second Sun", 2); // {6}{W} + // + // You may cast up to one target instant card and/or up to one target sorcery card from your graveyard each + // with mana value X or less without paying their mana costs. If a spell cast this way would be put into + // your graveyard, exile it instead. If X is 10 or more, copy each of those spells twice. You may choose + // new targets for the copies. + addCard(Zone.HAND, playerA, "Finale of Promise", 2); // {X}{R}{R} + // + addCard(Zone.BATTLEFIELD, playerA, "Mystic Monastery", 25); // for mana // first may have been cast from anywhere. castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Finale of Promise", true); - // You may cast up to one target instant card and/or up to one target sorcery card from your graveyard each with mana value X or less without paying their mana costs. If a spell cast this way would be put into your graveyard, exile it instead. If X is 10 or more, copy each of those spells twice. You may choose new targets for the copies. setChoice(playerA, "X=7"); // each with mana value X or less setChoice(playerA, "Yes"); // You may cast addTarget(playerA, TARGET_SKIP); // up to one target instant card diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/blb/CamelliaTheSeedmiserTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/blb/CamelliaTheSeedmiserTest.java index e080756ddfc..a3477bedf5a 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/blb/CamelliaTheSeedmiserTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/blb/CamelliaTheSeedmiserTest.java @@ -7,7 +7,6 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * * @author Grath */ public class CamelliaTheSeedmiserTest extends CardTestPlayerBase { @@ -57,7 +56,7 @@ public class CamelliaTheSeedmiserTest extends CardTestPlayerBase { waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); - activateAbility(3, PhaseStep.UPKEEP, playerA, "{2}, {T}, Sacrifice this artifact: You gain 3 life"); + activateAbility(3, PhaseStep.UPKEEP, playerA, "{2}, {T}, Sacrifice"); setStopAt(3, PhaseStep.BEGIN_COMBAT); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/c19/ChainerNightmareAdeptTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/c19/ChainerNightmareAdeptTest.java index 630721af3b9..6d8a2a917ed 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/c19/ChainerNightmareAdeptTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/c19/ChainerNightmareAdeptTest.java @@ -24,6 +24,7 @@ public class ChainerNightmareAdeptTest extends CardTestPlayerBase { addCard(Zone.GRAVEYARD, playerA, maaka, 2); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Discard"); + setChoice(playerA, mountain); // discard cost waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, maaka); @@ -33,8 +34,8 @@ public class ChainerNightmareAdeptTest extends CardTestPlayerBase { attack(1, playerA, maaka); + setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); - execute(); assertPermanentCount(playerA, maaka, 1); @@ -52,7 +53,10 @@ public class ChainerNightmareAdeptTest extends CardTestPlayerBase { addCard(Zone.GRAVEYARD, playerA, khenra); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Discard"); - setChoice(playerA, true); + setChoice(playerA, mountain); // discard cost + setChoice(playerA, true); // use copy + setChoice(playerA, "0"); // for casting main spell - x2 permitting object + setChoice(playerA, "0"); // for casting copied spell - x2 permitting object waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, maaka, true); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, khenra); @@ -60,8 +64,8 @@ public class ChainerNightmareAdeptTest extends CardTestPlayerBase { attack(1, playerA, maaka); attack(1, playerA, khenra); + setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); - execute(); assertPermanentCount(playerA, maaka, 1); @@ -88,8 +92,8 @@ public class ChainerNightmareAdeptTest extends CardTestPlayerBase { attack(1, playerA, maaka); + setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); - execute(); assertPermanentCount(playerA, maaka, 1); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/c21/IncarnationTechniqueTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/c21/IncarnationTechniqueTest.java new file mode 100644 index 00000000000..bdddf97060e --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/c21/IncarnationTechniqueTest.java @@ -0,0 +1,125 @@ +package org.mage.test.cards.single.c21; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ + +public class IncarnationTechniqueTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.i.IncarnationTechnique Incarnation Technique} {4}{B} + * Sorcery + * Demonstrate (When you cast this spell, you may copy it. If you do, choose an opponent to also copy it.) + * Mill five cards, then return a creature card from your graveyard to the battlefield. + */ + private static final String technique = "Incarnation Technique"; + + private static final String myr = "Omega Myr"; // vanilla 1/2 + + /** + * Emrakul, the Aeons Torn {15} + * Legendary Creature — Eldrazi + * This spell can’t be countered. + * When you cast this spell, take an extra turn after this one. + * Flying, protection from spells that are one or more colors, annihilator 6 + * When Emrakul is put into a graveyard from anywhere, its owner shuffles their graveyard into their library. + * 15/15 + */ + private static final String emrakul = "Emrakul, the Aeons Torn"; + + /** + * Cosi's Trickster {U} + * Creature — Merfolk Wizard + * Whenever an opponent shuffles their library, you may put a +1/+1 counter on this creature. + * 1/1 + */ + private static final String trickster = "Cosi's Trickster"; + + @Test + public void testNoCreatureToReturn() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.HAND, playerA, technique); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 5); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, technique); + setChoice(playerA, true); // yes to demonstrate + setChoice(playerA, playerB.getName()); // choosing B for demonstrate copy + + setStopAt(1, PhaseStep.END_COMBAT); + execute(); + + assertGraveyardCount(playerA, 5 * 2 + 1); + assertGraveyardCount(playerB, 5); + } + + @Test + public void testCreatureInGraveyard() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.HAND, playerA, technique); + addCard(Zone.GRAVEYARD, playerA, myr); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 5); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, technique); + setChoice(playerA, false); // no to demonstrate + setChoice(playerA, myr); // returned creature + + setStopAt(1, PhaseStep.END_COMBAT); + execute(); + + assertPermanentCount(playerA, myr, 1); + assertGraveyardCount(playerA, 5 + 1); + } + + @Test + public void testCreatureMilled() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.HAND, playerA, technique); + addCard(Zone.LIBRARY, playerA, myr); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 5); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, technique); + setChoice(playerA, false); // no to demonstrate + setChoice(playerA, myr); // returned creature + + setStopAt(1, PhaseStep.END_COMBAT); + execute(); + + assertPermanentCount(playerA, myr, 1); + assertGraveyardCount(playerA, 4 + 1); + } + + @Test + public void testEmrakul() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.HAND, playerA, technique); + addCard(Zone.BATTLEFIELD, playerB, trickster); + addCard(Zone.LIBRARY, playerA, emrakul); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 5); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, technique); + setChoice(playerA, false); // no to demonstrate + setChoice(playerA, emrakul); // returned creature + setChoice(playerB, true); // yes to Cosi's Trickster may trigger + + setStopAt(1, PhaseStep.END_COMBAT); + execute(); + + assertPermanentCount(playerA, emrakul, 1); + assertGraveyardCount(playerA, 0); + assertCounterCount(playerB, trickster, CounterType.P1P1, 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/clb/BabaLysagaNightWitchTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/clb/BabaLysagaNightWitchTest.java index a32ff3e595a..4787a8f9b5f 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/clb/BabaLysagaNightWitchTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/clb/BabaLysagaNightWitchTest.java @@ -3,6 +3,7 @@ package org.mage.test.cards.single.clb; 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; /** @@ -29,6 +30,7 @@ public class BabaLysagaNightWitchTest extends CardTestPlayerBase { activateAbility(1, PhaseStep.UPKEEP, playerA, "{1}: {this} becomes a 2/2 Assembly-Worker artifact creature until end of turn. It's still a land."); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}, Sacrifice up to three permanents: If there "); setChoice(playerA, "Mishra's Factory"); + setChoice(playerA, TestPlayer.CHOICE_SKIP); setStopAt(1, PhaseStep.PRECOMBAT_MAIN); execute(); @@ -48,6 +50,7 @@ public class BabaLysagaNightWitchTest extends CardTestPlayerBase { activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}, Sacrifice up to three permanents: If there "); setChoice(playerA, "Mishra's Factory"); + setChoice(playerA, TestPlayer.CHOICE_SKIP); setStopAt(1, PhaseStep.PRECOMBAT_MAIN); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/dft/KetramoseTheNewDawnTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/dft/KetramoseTheNewDawnTest.java index 452f9a80bf7..41ce65f7cf4 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/dft/KetramoseTheNewDawnTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/dft/KetramoseTheNewDawnTest.java @@ -40,6 +40,7 @@ public class KetramoseTheNewDawnTest extends CardTestPlayerBase { waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); checkLife("exile single - after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 20 - 1); + // exile multiple // must have two triggers: exile on cost and exile on resolve // exile on cost activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}, Exile {this}"); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/dft/MuYanlingWindRiderTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/dft/MuYanlingWindRiderTest.java index 882c97fdb57..d8e9a0a82e3 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/dft/MuYanlingWindRiderTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/dft/MuYanlingWindRiderTest.java @@ -5,6 +5,7 @@ import mage.constants.PhaseStep; import mage.constants.Zone; import mage.counters.CounterType; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; /** @@ -45,6 +46,7 @@ public class MuYanlingWindRiderTest extends CardTestPlayerBase { activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Crew 1"); setChoice(playerA, "Memnite"); + setChoice(playerA, TestPlayer.CHOICE_SKIP); attack(3, playerA, "Vehicle Token", playerB); attack(3, playerA, muyanling, playerB); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/dka/IncreasingCardsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/dka/IncreasingCardsTest.java index 7a5bb88a941..0d62b9fbc3c 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/dka/IncreasingCardsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/dka/IncreasingCardsTest.java @@ -56,6 +56,7 @@ public class IncreasingCardsTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Increasing Confusion"); activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Flashback {X}{U}"); + setStrictChooseMode(false); // TODO: good test for AI's announceX - duplicate it as AI test setStopAt(3, PhaseStep.BEGIN_COMBAT); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/dsk/ValgavothTerrorEaterTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/dsk/ValgavothTerrorEaterTest.java new file mode 100644 index 00000000000..dfa61239f2b --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/dsk/ValgavothTerrorEaterTest.java @@ -0,0 +1,369 @@ +package org.mage.test.cards.single.dsk; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class ValgavothTerrorEaterTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.v.ValgavothTerrorEater Valgavoth, Terror Eater} {6}{B}{B}{B} + * Legendary Creature — Elder Demon + * Flying, lifelink + * Ward—Sacrifice three nonland permanents. + * If a card you didn’t control would be put into an opponent’s graveyard from anywhere, exile it instead. + * During your turn, you may play cards exiled with Valgavoth. If you cast a spell this way, pay life equal to its mana value rather than pay its mana cost. + * 9/9 + */ + private static final String valgavoth = "Valgavoth, Terror Eater"; + + /** + * Donate {2}{U} Sorcery + * Target player gains control of target permanent you control. + */ + private static final String donate = "Donate"; + private static final String vanguard = "Elite Vanguard"; + private static final String piker = "Goblin Piker"; + private static final String bear = "Grizzly Bears"; + /** + * Lightning Bolt {R} Instant + * Lightning Bolt deals 3 damage to any target. + */ + private static final String bolt = "Lightning Bolt"; + /** + * Fire // Ice + * Fire {1}{R} Instant + * Fire deals 2 damage divided as you choose among one or two targets. + * Ice {1}{U} Instant + * Tap target permanent. + * Draw a card. + */ + private static final String fireice = "Fire // Ice"; + private static final String fire = "Fire"; + private static final String ice = "Ice"; + /** + * Cloudshift {W} Instant + * Exile target creature you control, then return that card to the battlefield under your control. + */ + private static final String cloudshift = "Cloudshift"; + /** + * Pyroclasm {1}{R} Sorcery + * Pyroclasm deals 2 damage to each creature. + */ + private static final String pyroclasm = "Pyroclasm"; + /** + * Legerdemain {2}{U}{U} Sorcery + * Exchange control of target artifact or creature and another target permanent that shares one of those types with it. + */ + private static final String legerdemain = "Legerdemain"; + /** + * Tome Scour {U} Sorcery + * Target player mills five cards. + */ + private static final String scour = "Tome Scour"; + + /** + * Akoum Warrior // Akoum Teeth + * Akoum Warrior {5}{R} + * Creature — Minotaur Warrior + * Trample + * 4/5 + * Akoum Teeth + * Land + * This land enters tapped. + * {T}: Add {R}. + */ + private static final String akoum = "Akoum Warrior // Akoum Teeth"; + private static final String warrior = "Akoum Warrior"; + private static final String teeth = "Akoum Teeth"; + + @Test + public void testOwnBolts() { + addCard(Zone.BATTLEFIELD, playerA, valgavoth, 1); + addCard(Zone.HAND, playerA, bolt, 3); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + addCard(Zone.BATTLEFIELD, playerB, vanguard, 1); + addCard(Zone.BATTLEFIELD, playerA, piker, 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bolt, playerB); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bolt, vanguard); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bolt, piker); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertExileCount(playerB, vanguard, 1); + assertGraveyardCount(playerA, bolt, 3); + assertGraveyardCount(playerA, piker, 1); + assertLife(playerB, 20 - 3); + } + + @Test + public void testOpponentBolts() { + addCard(Zone.BATTLEFIELD, playerA, valgavoth, 1); + addCard(Zone.HAND, playerB, bolt, 3); + addCard(Zone.BATTLEFIELD, playerB, "Mountain", 3); + addCard(Zone.BATTLEFIELD, playerB, vanguard, 1); + addCard(Zone.BATTLEFIELD, playerA, piker, 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, bolt, playerB); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, bolt, vanguard); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, bolt, piker); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertExileCount(playerB, vanguard, 1); + assertExileCount(playerB, bolt, 3); + assertGraveyardCount(playerA, piker, 1); + assertLife(playerB, 20 - 3); + } + + @Test + public void testAfterLegerdemainOthersA() { + addCard(Zone.BATTLEFIELD, playerA, valgavoth, 1); + addCard(Zone.HAND, playerA, pyroclasm, 1); + addCard(Zone.HAND, playerA, legerdemain, 1); + addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 6); + addCard(Zone.BATTLEFIELD, playerB, vanguard, 1); + addCard(Zone.BATTLEFIELD, playerA, piker, 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, legerdemain, vanguard + "^" + piker); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, pyroclasm); + // playerA controls vanguard, so it is not exiled + // piker goes into playerA's graveyard, so it is not exiled either + // pyroclasm and legerdemain have been cast by playerA, so they are not exiled. + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertExileCount(playerA, 0); + assertExileCount(playerB, 0); + assertGraveyardCount(playerA, 3); + assertGraveyardCount(playerB, 1); + } + + @Test + public void testAfterLegerdemainOthersB() { + addCard(Zone.BATTLEFIELD, playerB, valgavoth, 1); + addCard(Zone.HAND, playerA, pyroclasm, 1); + addCard(Zone.HAND, playerA, legerdemain, 1); + addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 6); + addCard(Zone.BATTLEFIELD, playerB, vanguard, 1); + addCard(Zone.BATTLEFIELD, playerA, piker, 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, legerdemain, vanguard + "^" + piker); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, pyroclasm); + // playerB controls piker, so it is not exiled + // vanguard goes into playerB's graveyard, so it is not exiled either + // pyroclasm and legerdemain have been cast by playerA, so they are exiled. + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertExileCount(playerA, 2); + assertExileCount(playerB, 0); + assertGraveyardCount(playerA, 1); + assertGraveyardCount(playerB, 1); + } + + @Test + public void testAfterLegerdemainOthersDonate() { + addCard(Zone.BATTLEFIELD, playerA, valgavoth, 1); + addCard(Zone.HAND, playerA, donate, 1); + addCard(Zone.HAND, playerA, pyroclasm, 1); + addCard(Zone.HAND, playerA, legerdemain, 1); + addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 9); + addCard(Zone.BATTLEFIELD, playerB, vanguard, 1); + addCard(Zone.BATTLEFIELD, playerA, piker, 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, donate, playerB); + addTarget(playerA, valgavoth); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + // Donate ends up exiled as valgavoth is in control of playerB as it finishes resolving + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, legerdemain, vanguard + "^" + piker); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, pyroclasm); + // playerB controls piker, so it is not exiled + // vanguard goes into playerB's graveyard, so it is not exiled either + // pyroclasm and legerdemain have been cast by playerA, so they are exiled. + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertExileCount(playerA, 3); + assertExileCount(playerB, 0); + assertGraveyardCount(playerA, 1); + assertGraveyardCount(playerB, 1); + } + + @Test + public void testMillSelf() { + addCard(Zone.BATTLEFIELD, playerA, valgavoth, 1); + addCard(Zone.HAND, playerA, scour, 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, scour, playerA); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertExileCount(playerA, 0); + assertExileCount(playerB, 0); + assertGraveyardCount(playerA, 6); + } + + @Test + public void testMillOpponent() { + addCard(Zone.BATTLEFIELD, playerA, valgavoth, 1); + addCard(Zone.HAND, playerA, scour, 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, scour, playerB); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertExileCount(playerA, 0); + assertExileCount(playerB, 5); + assertGraveyardCount(playerA, 1); + } + + @Test + public void testCastTimingAndSplitCard() { + addCard(Zone.BATTLEFIELD, playerA, valgavoth, 1); + addCard(Zone.HAND, playerB, fireice); + addCard(Zone.HAND, playerB, bolt); + addCard(Zone.BATTLEFIELD, playerB, "Volcanic Island", 6); + + castSpell(1, PhaseStep.UPKEEP, playerB, bolt, playerA); + checkPlayableAbility("1: Can cast bolt PlA", 1, PhaseStep.END_TURN, playerA, "Cast " + bolt, true); + checkPlayableAbility("1: Can not cast bolt PlB", 1, PhaseStep.END_TURN, playerB, "Cast " + bolt, false); + checkPlayableAbility("1: Can not cast fire PlA", 1, PhaseStep.END_TURN, playerA, "Cast " + fire, false); + checkPlayableAbility("1: Can cast fire PlB", 1, PhaseStep.END_TURN, playerB, "Cast " + fire, true); + checkPlayableAbility("1: Can not cast ice PlA", 1, PhaseStep.END_TURN, playerA, "Cast " + ice, false); + checkPlayableAbility("1: Can cast ice PlB", 1, PhaseStep.END_TURN, playerB, "Cast " + ice, true); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, fire, playerA); + addTargetAmount(playerB, playerA, 2); + checkPlayableAbility("2: Can not cast bolt PlA", 2, PhaseStep.END_TURN, playerA, "Cast " + bolt, false); + checkPlayableAbility("2: Can not cast bolt PlB", 2, PhaseStep.END_TURN, playerB, "Cast " + bolt, false); + checkPlayableAbility("2: Can not cast fire PlA", 2, PhaseStep.END_TURN, playerA, "Cast " + fire, false); + checkPlayableAbility("2: Can not cast fire PlB", 2, PhaseStep.END_TURN, playerB, "Cast " + fire, false); + checkPlayableAbility("2: Can not cast ice PlA", 2, PhaseStep.END_TURN, playerA, "Cast " + ice, false); + checkPlayableAbility("2: Can not cast ice PlB", 2, PhaseStep.END_TURN, playerB, "Cast " + ice, false); + + waitStackResolved(3, PhaseStep.UPKEEP); + checkPlayableAbility("3: Can cast bolt PlA", 3, PhaseStep.END_TURN, playerA, "Cast " + bolt, true); + checkPlayableAbility("3: Can not cast bolt PlB", 2, PhaseStep.END_TURN, playerB, "Cast " + bolt, false); + checkPlayableAbility("3: Can cast fire PlA", 3, PhaseStep.END_TURN, playerA, "Cast " + fire, true); + checkPlayableAbility("3: Can not cast fire PlB", 2, PhaseStep.END_TURN, playerB, "Cast " + fire, false); + checkPlayableAbility("3: Can cast ice PlA", 3, PhaseStep.END_TURN, playerA, "Cast " + ice, true); + checkPlayableAbility("3: Can not cast ice PlB", 2, PhaseStep.END_TURN, playerB, "Cast " + ice, false); + castSpell(3, PhaseStep.END_TURN, playerA, bolt, playerB); + castSpell(3, PhaseStep.END_TURN, playerA, fire, playerB); + addTargetAmount(playerA, playerB, 2); + + setStopAt(4, PhaseStep.UPKEEP); + setStrictChooseMode(true); + execute(); + + assertGraveyardCount(playerA, 0); + assertGraveyardCount(playerB, 2); + assertExileCount(playerA, 0); + assertExileCount(playerB, 0); + + assertLife(playerA, 20 - 3 - 2 - 1 - 2); + assertLife(playerB, 20 - 3 - 2); + } + + @Test + public void testCastMDFC() { + skipInitShuffling(); + addCard(Zone.BATTLEFIELD, playerA, valgavoth, 1); + addCard(Zone.LIBRARY, playerB, akoum, 5); + addCard(Zone.HAND, playerA, scour); + addCard(Zone.BATTLEFIELD, playerA, "Island"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, scour, playerB); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + checkPlayableAbility("1: Can cast Akoum Warrior", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + warrior, true); + checkPlayableAbility("1: Can play Akoum Teeth", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Play " + teeth, true); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, warrior); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, warrior); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, warrior); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + checkPlayableAbility("2: Can not cast Akoum Warrior (not enough life)", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + warrior, false); + checkPlayableAbility("2: Can play Akoum Teeth", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Play " + teeth, true); + + playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, teeth); + checkPlayableAbility("3: Can not cast Akoum Warrior (not enough life)", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + warrior, false); + checkPlayableAbility("3: Can not play Akoum Teeth (no land drop left)", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Play " + teeth, false); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertExileCount(playerA, 0); + assertExileCount(playerB, 1); + + assertLife(playerA, 20 - 6 * 3); + assertPermanentCount(playerA, warrior, 3); + assertPermanentCount(playerA, teeth, 1); + } + + @Test + public void testWardAndBlink() { + addCard(Zone.BATTLEFIELD, playerA, valgavoth, 1); + addCard(Zone.BATTLEFIELD, playerB, bear, 1); + addCard(Zone.BATTLEFIELD, playerB, vanguard, 1); + addCard(Zone.BATTLEFIELD, playerB, piker, 1); + addCard(Zone.HAND, playerB, bolt, 1); + addCard(Zone.BATTLEFIELD, playerB, "Mountain", 1); + addCard(Zone.HAND, playerA, cloudshift, 1); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, bolt, valgavoth); + setChoice(playerB, true); // pay for ward? + setChoice(playerB, bear + "^" + vanguard + "^" + piker); // ward sacrifices + + checkPlayableAbility("1: Can cast bear", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast " + bear, true); + checkPlayableAbility("1: Can cast vanguard", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast " + vanguard, true); + checkPlayableAbility("1: Can cast piker", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast " + piker, true); + checkPlayableAbility("1: Can cast bolt", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast " + bolt, true); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, cloudshift, valgavoth); + waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN); + + // after blink, nothing can be cast + checkPlayableAbility("2: Can not cast bear", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast " + bear, false); + checkPlayableAbility("2: Can not cast vanguard", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast " + vanguard, false); + checkPlayableAbility("2: Can not cast piker", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast " + piker, false); + checkPlayableAbility("2: Can not cast bolt", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast " + bolt, false); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertExileCount(playerA, 0); + assertExileCount(playerB, 4); + assertGraveyardCount(playerA, 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/emn/TamiyoFieldResearcherTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/emn/TamiyoFieldResearcherTest.java index 812090aab8a..8f5316c9411 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/emn/TamiyoFieldResearcherTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/emn/TamiyoFieldResearcherTest.java @@ -4,6 +4,7 @@ package org.mage.test.cards.single.emn; 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; /** @@ -77,6 +78,7 @@ public class TamiyoFieldResearcherTest extends CardTestPlayerBase { activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1: Choose up to two"); addTarget(playerA, "Bronze Sable"); + addTarget(playerA, TestPlayer.TARGET_SKIP); attack(1, playerA, "Bronze Sable"); @@ -135,6 +137,7 @@ public class TamiyoFieldResearcherTest extends CardTestPlayerBase { activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1: Choose up to two"); addTarget(playerA, "Sylvan Advocate"); + addTarget(playerA, TestPlayer.TARGET_SKIP); attack(1, playerA, "Sylvan Advocate"); attack(2, playerB, "Memnite"); @@ -167,6 +170,7 @@ public class TamiyoFieldResearcherTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tamiyo, Field Researcher", true); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1: Choose up to two"); addTarget(playerA, "Sylvan Advocate"); + addTarget(playerA, TestPlayer.TARGET_SKIP); attack(1, playerA, "Sylvan Advocate"); @@ -236,6 +240,7 @@ public class TamiyoFieldResearcherTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tamiyo, Field Researcher", true); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1: Choose up to two"); addTarget(playerA, "Bronze Sable"); + addTarget(playerA, TestPlayer.TARGET_SKIP); attack(2, playerB, "Bronze Sable"); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/fic/HildibrandMandervilleTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/fic/HildibrandMandervilleTest.java new file mode 100644 index 00000000000..71de8da0598 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/fic/HildibrandMandervilleTest.java @@ -0,0 +1,77 @@ +package org.mage.test.cards.single.fic; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class HildibrandMandervilleTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.h.HildibrandManderville Hildibrand Manderville} {1}{W} + * Legendary Creature — Human Detective + * Creature tokens you control get +1/+1. + * When Hildibrand Manderville dies, you may cast it from your graveyard as an Adventure until the end of your next turn. + *

+ * Gentleman's Rise {2}{B} + * Instant — Adventure + * Create a 2/2 black Zombie creature token. (Then exile this card. You may cast the creature later from exile.) + */ + private static final String hildibrand = "Hildibrand Manderville"; + private static final String rise = "Gentleman's Rise"; + + /** + * {1}{B} Instant Destroy target non-black creature. + */ + private static final String blade = "Doom Blade"; + + @Test + public void test_CastFromGraveyard() { + addCard(Zone.BATTLEFIELD, playerA, hildibrand, 1); + addCard(Zone.HAND, playerA, blade, 1); + addCard(Zone.BATTLEFIELD, playerA, "Scrubland", 5); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, blade, hildibrand); + castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerA, rise); + + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, hildibrand); + + setStopAt(3, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertPermanentCount(playerA, hildibrand, 1); + assertTokenCount(playerA, "Zombie Token", 1); + } + + @Test + public void test_CheckOnlyAsAdventureAndTimingForRise() { + addCard(Zone.BATTLEFIELD, playerA, hildibrand, 1); + addCard(Zone.HAND, playerA, blade, 1); + addCard(Zone.BATTLEFIELD, playerA, "Scrubland", 5); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, blade, hildibrand); + + checkPlayableAbility("1: rise: right after", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast " + rise, true); + checkPlayableAbility("1: hildibrand: right after", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast " + hildibrand, false); + + checkPlayableAbility("2: rise: on opp turn", 2, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast " + rise, true); + checkPlayableAbility("2: hildibrand: on opp turn", 2, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast " + hildibrand, false); + + checkPlayableAbility("3: rise: on your next turn", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + rise, true); + checkPlayableAbility("3: hildibrand: on your next turn", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + hildibrand, false); + + checkPlayableAbility("4: rise: after duration", 4, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + rise, false); + checkPlayableAbility("4: hildibrand: after duration", 4, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + hildibrand, false); + + setStopAt(4, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertGraveyardCount(playerA, hildibrand, 1); + assertTokenCount(playerA, "Zombie Token", 0); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/fin/ViviOrnitierTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/fin/ViviOrnitierTest.java new file mode 100644 index 00000000000..96cebf3da81 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/fin/ViviOrnitierTest.java @@ -0,0 +1,65 @@ +package org.mage.test.cards.single.fin; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class ViviOrnitierTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.v.ViviOrnitier Vivi Ornitier} {1}{U}{R} + * Legendary Creature — Wizard + * {0}: Add X mana in any combination of {U} and/or {R}, where X is Vivi Ornitier’s power. Activate only during your turn and only once each turn. + * Whenever you cast a noncreature spell, put a +1/+1 counter on Vivi Ornitier and it deals 1 damage to each opponent. + * 0/3 + */ + private static final String vivi = "Vivi Ornitier"; + + /** + * Creatures you control get +2/+2. + */ + private static final String dictate = "Dictate of Heliod"; + + private static final String bolt = "Lightning Bolt"; + private static final String incinerate = "Incinerate"; + + @Test + public void test_NoPower() { + addCard(Zone.BATTLEFIELD, playerA, vivi, 1); + addCard(Zone.HAND, playerA, bolt); + + checkPlayableAbility("bolt can not be cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + bolt, false); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + setStrictChooseMode(true); + execute(); + } + + @Test + public void test_2Power() { + addCard(Zone.BATTLEFIELD, playerA, vivi, 1); + addCard(Zone.BATTLEFIELD, playerA, dictate, 1); + addCard(Zone.HAND, playerA, bolt); + addCard(Zone.HAND, playerA, incinerate); + + checkPlayableAbility("1: bolt can be cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + bolt, true); + checkPlayableAbility("1: incinerate can be cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + incinerate, true); + + setChoice(playerA, "X=0"); // choose {U} color distribution for vivi on 2 power + setChoice(playerA, "X=2"); // choose {R} color distribution for vivi on 2 power + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, incinerate, playerB); + + checkPlayableAbility("2: bolt can not be cast", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast " + bolt, false); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertLife(playerB, 20 - 3 - 1); + assertPowerToughness(playerA, vivi, 3, 6); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/grn/WandOfVertebraeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/grn/WandOfVertebraeTest.java index 0749f8884c5..66824c5bb01 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/grn/WandOfVertebraeTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/grn/WandOfVertebraeTest.java @@ -3,6 +3,7 @@ package org.mage.test.cards.single.grn; 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; /** @@ -31,6 +32,7 @@ public class WandOfVertebraeTest extends CardTestPlayerBase { activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}, {T}"); addTarget(playerA, lavaCoil); + addTarget(playerA, TestPlayer.TARGET_SKIP); // must choose 1 of 5 setStopAt(1, PhaseStep.PRECOMBAT_MAIN); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/j22/AgrusKosEternalSoldierTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/j22/AgrusKosEternalSoldierTest.java index 349dda8af12..0a33e87222b 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/j22/AgrusKosEternalSoldierTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/j22/AgrusKosEternalSoldierTest.java @@ -3,6 +3,7 @@ package org.mage.test.cards.single.j22; 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; /** @@ -80,13 +81,17 @@ public class AgrusKosEternalSoldierTest extends CardTestPlayerBase { addCard(Zone.HAND, playerA, "Smoldering Werewolf"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Smoldering Werewolf"); - setChoice(playerA, "When {this} enters, it deals"); - addTarget(playerA, agrus); - addTarget(playerA, agrus); - setChoice(playerB, true); // gain life - setChoice(playerB, "Whenever {this} becomes"); - setChoice(playerB, true); // pay to copy - setChoice(playerB, true); // pay to copy + setChoice(playerB, true); // gain life on cast + // + setChoice(playerA, "When {this} enters, it deals"); // x2 triggers from Panharmonicon + addTarget(playerA, agrus); // x1 trigger + addTarget(playerA, TestPlayer.TARGET_SKIP); // x1 trigger + addTarget(playerA, agrus); // x2 trigger + addTarget(playerA, TestPlayer.TARGET_SKIP); // x2 trigger + // + setChoice(playerB, "Whenever {this} becomes"); // x2 triggers from Panharmonicon + setChoice(playerB, true); // x1 pay to copy + setChoice(playerB, true); // x2 pay to copy setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/ltr/HELIOSOneTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/ltr/HELIOSOneTest.java index cd19de6bbda..366176c3c01 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/ltr/HELIOSOneTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/ltr/HELIOSOneTest.java @@ -29,7 +29,7 @@ public class HELIOSOneTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Plains", 3); addCard(Zone.BATTLEFIELD, playerA, "Memnite"); - setChoice(playerA, "X=0"); + //setChoice(playerA, "X=0"); // auto-choose X=0 activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}"); addTarget(playerA, "Memnite"); @@ -52,7 +52,10 @@ public class HELIOSOneTest extends CardTestPlayerBase { // TODO: So the test suite let's you activate the ability (as it does not go to adjust targets to check them.) // But X=0 is not a valid choice once targets are checked (no nonland card with that MV in play). - setChoice(playerA, "X=0"); + checkPlayableAbility("Pay X {E} ability is playable, but can't be activated", + 1, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}", true); + showAvailableAbilities("hmm", 1, PhaseStep.PRECOMBAT_MAIN, playerA); + //setChoice(playerA, "X=0"); // auto-choose X=0 waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}"); addTarget(playerA, "Elite Vanguard"); // not a valid target for X=0 energy payment @@ -65,7 +68,7 @@ public class HELIOSOneTest extends CardTestPlayerBase { } catch (AssertionError e) { Assert.assertTrue( "X=0 is not a valid choice. Error message:\n" + e.getMessage(), - e.getMessage().contains("Message: Announce the number of {E} to pay") + e.getMessage().contains("Can't find ability to activate command: {3}") ); } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/m21/AlpineHoundmasterTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/m21/AlpineHoundmasterTest.java index 64008416185..329cd5247be 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/m21/AlpineHoundmasterTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/m21/AlpineHoundmasterTest.java @@ -3,6 +3,7 @@ package org.mage.test.cards.single.m21; 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; public class AlpineHoundmasterTest extends CardTestPlayerBase { @@ -22,6 +23,7 @@ public class AlpineHoundmasterTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Alpine Houndmaster"); setChoice(playerA, true); addTarget(playerA, "Alpine Watchdog"); + addTarget(playerA, TestPlayer.TARGET_SKIP); // only single card setStrictChooseMode(true); setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); @@ -44,6 +46,7 @@ public class AlpineHoundmasterTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Alpine Houndmaster"); setChoice(playerA, true); addTarget(playerA, "Igneous Cur"); + addTarget(playerA, TestPlayer.TARGET_SKIP); // only single card setStrictChooseMode(true); setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); @@ -54,7 +57,7 @@ public class AlpineHoundmasterTest extends CardTestPlayerBase { } @Test - public void searchBoth() { + public void searchBoth_TestFramework_AddTargetsAsSingle() { // When Alpine Houndmaster enters the battlefield, you may search your library for a card named // Alpine Watchdog and/or a card named Igneous Cur, reveal them, put them into your hand, then shuffle your library. addCard(Zone.HAND, playerA, "Alpine Houndmaster", 1); @@ -72,7 +75,30 @@ public class AlpineHoundmasterTest extends CardTestPlayerBase { execute(); assertHandCount(playerA, 2); + } + @Test + public void searchBoth_TestFramework_AddTargetsAsMultiple() { + // test framework must support both + + // When Alpine Houndmaster enters the battlefield, you may search your library for a card named + // Alpine Watchdog and/or a card named Igneous Cur, reveal them, put them into your hand, then shuffle your library. + addCard(Zone.HAND, playerA, "Alpine Houndmaster", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + addCard(Zone.BATTLEFIELD, playerA, "Plains"); + addCard(Zone.LIBRARY, playerA, "Alpine Watchdog"); + addCard(Zone.LIBRARY, playerA, "Igneous Cur"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Alpine Houndmaster"); + setChoice(playerA, true); + addTarget(playerA, "Igneous Cur"); + addTarget(playerA, "Alpine Watchdog"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertHandCount(playerA, 2); } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/NethergoyfTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/NethergoyfTest.java index 8d2db6405d7..658445c4daa 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/NethergoyfTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/NethergoyfTest.java @@ -3,6 +3,7 @@ package org.mage.test.cards.single.mh3; import mage.constants.PhaseStep; import mage.constants.Zone; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBaseWithAIHelps; /** @@ -72,6 +73,7 @@ public class NethergoyfTest extends CardTestPlayerBaseWithAIHelps { checkPlayableAbility("can escape", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + nethergoyf + " with Escape", true); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, nethergoyf + " with Escape"); setChoice(playerA, "Memnite^Memnite^Memnite^Memnite^Bitterblossom"); // cards exiled for escape cost: Exile all the Memnite but one. + setChoice(playerA, TestPlayer.CHOICE_SKIP); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/SorinOfHouseMarkovTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/SorinOfHouseMarkovTest.java index 54e85ee8fdf..4b432ffa017 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/SorinOfHouseMarkovTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/SorinOfHouseMarkovTest.java @@ -87,8 +87,8 @@ public class SorinOfHouseMarkovTest extends CardTestPlayerBase { activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "+2"); // Activate both foods - activateAbility(5, PhaseStep.UPKEEP, playerA, "{2}, {T}, Sacrifice this artifact: You gain 3 life"); - activateAbility(5, PhaseStep.UPKEEP, playerA, "{2}, {T}, Sacrifice this artifact: You gain 3 life"); + activateAbility(5, PhaseStep.UPKEEP, playerA, "{2}, {T}, Sacrifice"); + activateAbility(5, PhaseStep.UPKEEP, playerA, "{2}, {T}, Sacrifice"); activateAbility(5, PhaseStep.PRECOMBAT_MAIN, playerA, "-1"); addTarget(playerA, playerB); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/SuppressionRayTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/SuppressionRayTest.java index 17909938b02..16905dcbf74 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/SuppressionRayTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/SuppressionRayTest.java @@ -4,6 +4,7 @@ import mage.constants.PhaseStep; import mage.constants.Zone; import mage.counters.CounterType; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; /** @@ -39,6 +40,7 @@ public class SuppressionRayTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Suppression Ray", playerB); setChoiceAmount(playerA, 3); // decide to pay 3 energy setChoice(playerA, "Zodiac Pig^Zodiac Rabbit"); // put stun on those 2 creatures + setChoice(playerA, TestPlayer.CHOICE_SKIP); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mir/TombstoneStairwellTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mir/TombstoneStairwellTest.java new file mode 100644 index 00000000000..04488e5b7a6 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mir/TombstoneStairwellTest.java @@ -0,0 +1,59 @@ +package org.mage.test.cards.single.mir; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ +public class TombstoneStairwellTest extends CardTestPlayerBase { + + @Test + public void test_LeavesTheBattlefield() { + addCustomEffect_TargetDestroy(playerA); + + // Cumulative upkeep {1}{B} (At the beginning of your upkeep, put an age counter on this permanent, + // then sacrifice it unless you pay its upkeep cost for each age counter on it.) + // + // At the beginning of each upkeep, if Tombstone Stairwell is on the battlefield, each player creates + // a 2/2 black Zombie creature token with haste named Tombspawn for each creature card in their graveyard. + // + // At the beginning of each end step or when Tombstone Stairwell leaves the battlefield, destroy all tokens + // created with Tombstone Stairwell. They can't be regenerated. + addCard(Zone.BATTLEFIELD, playerA, "Tombstone Stairwell"); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); + + addCard(Zone.GRAVEYARD, playerA, "Grizzly Bears", 2); + addCard(Zone.GRAVEYARD, playerB, "Grizzly Bears", 3); + + // Both the cumulative upkeep and the triggered ability trigger at the beginning of upkeep, so you can choose + // what order they resolve. + // (2004-10-04) + + // turn 1 - pay upkeep and create tokens + setChoice(playerA, "Cumulative upkeep"); // x2 triggers: create tokens + upkeep pay + setChoice(playerA, "Yes"); // pay upkeep + checkPermanentTapped("turn 1 - pays done", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Swamp", true, 2); + checkPermanentCount("turn 1 - tokens A", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tombspawn", 2); + checkPermanentCount("turn 1 - tokens B", 1, PhaseStep.PRECOMBAT_MAIN, playerB, "Tombspawn", 3); + + // turn 1 - make sure tokens auto-destroyed + waitStackResolved(1, PhaseStep.END_TURN); + checkPermanentCount("turn 1 end - tokens A", 1, PhaseStep.END_TURN, playerA, "Tombspawn", 0); + checkPermanentCount("turn 1 end - tokens B", 1, PhaseStep.END_TURN, playerB, "Tombspawn", 0); + + // turn 2 - keep create and destroy tokens on leave + checkPermanentCount("turn 2 - tokens A", 2, PhaseStep.PRECOMBAT_MAIN, playerA, "Tombspawn", 2); + checkPermanentCount("turn 2 - tokens B", 2, PhaseStep.PRECOMBAT_MAIN, playerB, "Tombspawn", 3); + activateAbility(2, PhaseStep.BEGIN_COMBAT, playerA, "target destroy", "Tombstone Stairwell"); + waitStackResolved(2, PhaseStep.BEGIN_COMBAT); + checkPermanentCount("turn 2 after destroy - tokens A", 2, PhaseStep.POSTCOMBAT_MAIN, playerA, "Tombspawn", 0); + checkPermanentCount("turn 2 after destroy - tokens B", 2, PhaseStep.POSTCOMBAT_MAIN, playerB, "Tombspawn", 0); + + setStrictChooseMode(true); + setStopAt(2, PhaseStep.END_TURN); + execute(); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/CovetedFalconTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/CovetedFalconTest.java index fd367a14e24..926d936d4a4 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/CovetedFalconTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/CovetedFalconTest.java @@ -2,8 +2,10 @@ package org.mage.test.cards.single.mkm; import mage.constants.PhaseStep; import mage.constants.Zone; +import mage.target.TargetPlayer; import org.junit.Ignore; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; public class CovetedFalconTest extends CardTestPlayerBase { @@ -31,6 +33,7 @@ public class CovetedFalconTest extends CardTestPlayerBase { activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{U}: Turn this face-down permanent face up."); addTarget(playerA, playerB); addTarget(playerA, "Grizzly Bears"); + addTarget(playerA, TestPlayer.TARGET_SKIP); setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); @@ -57,6 +60,7 @@ public class CovetedFalconTest extends CardTestPlayerBase { activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{U}: Turn this face-down permanent face up."); addTarget(playerA, playerB); addTarget(playerA, "Grizzly Bears"); + addTarget(playerA, TestPlayer.TARGET_SKIP); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, true); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Turn Against", "Grizzly Bears"); @@ -82,6 +86,7 @@ public class CovetedFalconTest extends CardTestPlayerBase { activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{U}: Turn this face-down permanent face up."); addTarget(playerA, playerB); addTarget(playerA, "Putrid Goblin"); + addTarget(playerA, TestPlayer.TARGET_SKIP); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, true); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Murder", "Putrid Goblin"); @@ -111,6 +116,7 @@ public class CovetedFalconTest extends CardTestPlayerBase { activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{U}: Turn this face-down permanent face up."); addTarget(playerA, playerB); addTarget(playerA, "Treacherous Pit-Dweller"); + addTarget(playerA, TestPlayer.TARGET_SKIP); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, true); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Murder", "Treacherous Pit-Dweller"); @@ -151,6 +157,7 @@ public class CovetedFalconTest extends CardTestPlayerBase { activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{U}: Turn this face-down permanent face up."); addTarget(playerA, playerB); addTarget(playerA, "Darksteel Relic^Grizzly Bears"); + addTarget(playerA, TestPlayer.TARGET_SKIP); castSpell(1, PhaseStep.BEGIN_COMBAT, playerB, "Murder", "Guardian Beast"); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/TenthDistrictHeroTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/TenthDistrictHeroTest.java index 0a54b4ded33..ceb320da09f 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/TenthDistrictHeroTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/TenthDistrictHeroTest.java @@ -5,6 +5,7 @@ import mage.constants.PhaseStep; import mage.constants.SubType; import mage.constants.Zone; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; /** @@ -65,6 +66,7 @@ public class TenthDistrictHeroTest extends CardTestPlayerBase { activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}"); setChoice(playerA, giant); + setChoice(playerA, TestPlayer.CHOICE_SKIP); setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); @@ -92,6 +94,7 @@ public class TenthDistrictHeroTest extends CardTestPlayerBase { activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}"); setChoice(playerA, giant); + setChoice(playerA, TestPlayer.CHOICE_SKIP); setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/ncc/SinisterConciergeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/ncc/SinisterConciergeTest.java index 1aef884b088..ba02d5587e5 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/ncc/SinisterConciergeTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/ncc/SinisterConciergeTest.java @@ -36,7 +36,7 @@ public class SinisterConciergeTest extends CardTestPlayerBase { setStrictChooseMode(true); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, lightningBolt, sinisterConcierge); - setChoice(playerA, "Yes"); + setChoice(playerA, true); addTarget(playerA, bondedConstruct); setStopAt(1, PhaseStep.END_COMBAT); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/pip/TheWiseMothmanTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/pip/TheWiseMothmanTest.java index 342cbde4d91..963972ee0b0 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/pip/TheWiseMothmanTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/pip/TheWiseMothmanTest.java @@ -4,6 +4,7 @@ import mage.constants.PhaseStep; import mage.constants.Zone; import mage.counters.CounterType; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; /** @@ -37,6 +38,7 @@ public class TheWiseMothmanTest extends CardTestPlayerBase { activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}"); addTarget(playerA, mothman + "^Grizzly Bears"); // up to three targets => choosing 2 + addTarget(playerA, TestPlayer.TARGET_SKIP); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/shm/DevotedDruidTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/shm/DevotedDruidTest.java new file mode 100644 index 00000000000..cbe3b5ef5cf --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/shm/DevotedDruidTest.java @@ -0,0 +1,94 @@ +package org.mage.test.cards.single.shm; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ +public class DevotedDruidTest extends CardTestPlayerBase { + + @Test + public void test_PutCounter_Normal() { + // {T}: Add {G}. + // Put a -1/-1 counter on this creature: Untap this creature. + addCard(Zone.BATTLEFIELD, playerA, "Devoted Druid", 1); // 0/2 + + // prepare + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {G}"); + checkPermanentTapped("after mana tapped", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Devoted Druid", true, 1); + + // add counter and untap + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Put a -1/-1"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentTapped("after untap", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Devoted Druid", false, 1); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + } + + @Test + public void test_PutCounter_CantPay() { + // {T}: Add {G}. + // Put a -1/-1 counter on this creature: Untap this creature. + addCard(Zone.BATTLEFIELD, playerA, "Devoted Druid", 1); // 0/2 + // + // Players can’t get counters. + // Counters can’t be put on artifacts, creatures, enchantments, or lands. + addCard(Zone.BATTLEFIELD, playerA, "Solemnity", 1); + + // If you can't put -1/-1 counters on Devoted Druid (due to an effect such as that of Solemnity), + // you can't activate its second ability. + // ... + // (2018-12-07) + + //checkPlayableAbility("can't put counters", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Put a -1/-1", false); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Put a -1/-1"); + + setStopAt(1, PhaseStep.END_TURN); + // TODO: improve PutCountersSourceCost, so it can find real playable ability here instead restriction + try { + setStrictChooseMode(true); + execute(); + Assert.fail("must throw exception on execute"); + } catch (Throwable e) { + if (!e.getMessage().contains("Put a -1/-1")) { + Assert.fail("Needed error about not being able to use the Devoted Druid's -1/-1 ability, but got:\n" + e.getMessage()); + } + } + } + + @Test + @Ignore // TODO: must fix, see #13583 + public void test_PutCounter_ModifiedToZeroCounters() { + // {T}: Add {G}. + // Put a -1/-1 counter on this creature: Untap this creature. + addCard(Zone.BATTLEFIELD, playerA, "Devoted Druid", 1); // 0/2 + // + // If one or more -1/-1 counters would be put on a creature you control, that many -1/-1 counters + // minus one are put on it instead. + addCard(Zone.BATTLEFIELD, playerA, "Vizier of Remedies", 1); + + + // ... + // If you can put counters on it, but that is modified by an effect (such as that of Vizier of Remedies), + // you can activate the ability even if paying the cost causes no counters to be put on Devoted Druid. + // (2018-12-07) + + // prepare + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {G}"); + checkPermanentTapped("after mana tapped", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Devoted Druid", true, 1); + + // add counter and untap + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Put a -1/-1"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentTapped("after untap", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Devoted Druid", false, 1); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/asthough/CastAsInstantTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/targets/AutoChooseTargetsAndCastAsInstantTest.java similarity index 51% rename from Mage.Tests/src/test/java/org/mage/test/cards/asthough/CastAsInstantTest.java rename to Mage.Tests/src/test/java/org/mage/test/cards/targets/AutoChooseTargetsAndCastAsInstantTest.java index 5c32c90f343..3f0f7c86144 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/asthough/CastAsInstantTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/targets/AutoChooseTargetsAndCastAsInstantTest.java @@ -1,5 +1,4 @@ - -package org.mage.test.cards.asthough; +package org.mage.test.cards.targets; import mage.constants.PhaseStep; import mage.constants.Zone; @@ -7,13 +6,11 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * - * @author LevelX2 + * @author LevelX2, JayDi85 */ -public class CastAsInstantTest extends CardTestPlayerBase { +public class AutoChooseTargetsAndCastAsInstantTest extends CardTestPlayerBase { - @Test - public void testEffectOnlyForOneTurn() { + private void run_WithAutoSelection(int selectedTargets, int possibleTargets) { addCard(Zone.BATTLEFIELD, playerB, "Island"); addCard(Zone.BATTLEFIELD, playerB, "Swamp", 4); // The next sorcery card you cast this turn can be cast as though it had flash. @@ -23,22 +20,45 @@ public class CastAsInstantTest extends CardTestPlayerBase { // Target opponent exiles two cards from their hand and loses 2 life. addCard(Zone.HAND, playerB, "Witness the End"); // {3}{B} - addCard(Zone.HAND, playerA, "Silvercoat Lion", 2); + addCard(Zone.HAND, playerA, "Silvercoat Lion", possibleTargets); castSpell(1, PhaseStep.UPKEEP, playerB, "Quicken", true); castSpell(1, PhaseStep.UPKEEP, playerB, "Witness the End", playerA); + // it uses auto-choose logic inside, so disable strict mode + // logic for possible targets with min/max = 2: + // 0, 1, 2 - auto-choose all possible targets + // 3+ - AI choose best targets + setStrictChooseMode(false); setStopAt(1, PhaseStep.PRECOMBAT_MAIN); execute(); assertGraveyardCount(playerB, "Quicken", 1); assertGraveyardCount(playerB, "Witness the End", 1); - assertExileCount("Silvercoat Lion", 2); + assertExileCount("Silvercoat Lion", selectedTargets); assertLife(playerA, 18); assertLife(playerB, 20); - } + @Test + public void test_AutoChoose_0_of_0() { + run_WithAutoSelection(0, 0); + } + + @Test + public void test_AutoChoose_1_of_1() { + run_WithAutoSelection(1, 1); + } + + @Test + public void test_AutoChoose_2_of_2() { + run_WithAutoSelection(2, 2); + } + + @Test + public void test_AutoChoose_2_of_5() { + run_WithAutoSelection(2, 5); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/targets/TargetsSelectionBaseTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/targets/TargetsSelectionBaseTest.java new file mode 100644 index 00000000000..7681ef14fd4 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/targets/TargetsSelectionBaseTest.java @@ -0,0 +1,258 @@ +package org.mage.test.cards.targets; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapAttachedCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.cards.CardsImpl; +import mage.constants.Outcome; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; +import mage.target.Target; +import mage.target.common.TargetCardInHand; +import org.mage.test.player.TestPlayer; +import org.mage.test.serverside.base.CardTestPlayerBase; +import org.mage.test.serverside.base.CardTestPlayerBaseWithAIHelps; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.IntStream; + +/** + * Helper class for target selection tests in diff use cases like selection on targets declare or on resolve + * + * @author JayDi85 + */ +public class TargetsSelectionBaseTest extends CardTestPlayerBaseWithAIHelps { + + static final boolean DEBUG_ENABLE_DETAIL_LOGS = true; + + protected void run_PlayerChooseTarget_OnActivate(int chooseCardsCount, int availableCardsCount) { + // custom effect - exile and gain life due selected targets + int minTarget = 0; + int maxTarget = 3; + String startingText = "exile and gain"; + Ability ability = new SimpleActivatedAbility( + new ExileTargetEffect().setText("exile"), + new ManaCostsImpl<>("") + ); + ability.addEffect(new GainLifeEffect(TotalTargetsValue.instance).concatBy("and").setText("gain life")); + ability.addTarget(new TargetCardInHand(minTarget, maxTarget, StaticFilters.FILTER_CARD).withNotTarget(false)); + addCustomCardWithAbility("test choice", playerA, ability); + + addCard(Zone.HAND, playerA, "Swamp", availableCardsCount); + + checkHandCardCount("prepare", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Swamp", availableCardsCount); + checkExileCount("prepare", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Swamp", 0); + + if (availableCardsCount > 0) { + checkPlayableAbility("can activate on non-zero targets", 1, PhaseStep.PRECOMBAT_MAIN, playerA, startingText, true); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, startingText); + } else { + checkPlayableAbility("can't activate on zero targets", 1, PhaseStep.PRECOMBAT_MAIN, playerA, startingText, false); + } + + if (chooseCardsCount > 0) { + // need selection + List targetCards = new ArrayList<>(); + IntStream.rangeClosed(1, chooseCardsCount).forEach(x -> { + targetCards.add("Swamp"); + }); + addTarget(playerA, String.join("^", targetCards)); + // end selection: + // - x of 0 - no + // - 1 of 3 - yes + // - 2 of 3 - yes + // - 3 of 3 - no, it's auto-finish on last select + // - 3 of 5 - no, it's auto-finish on last select + if (chooseCardsCount < maxTarget) { + addTarget(playerA, TestPlayer.TARGET_SKIP); + } + } else { + // need skip + // on 0 cards there are not valid targets, so no any dialogs + if (availableCardsCount > 0) { + addTarget(playerA, TestPlayer.TARGET_SKIP); + } + } + + if (DEBUG_ENABLE_DETAIL_LOGS) { + System.out.println("planning actions:"); + playerA.getActions().forEach(System.out::println); + System.out.println("planning targets:"); + playerA.getTargets().forEach(System.out::println); + System.out.println("planning choices:"); + playerA.getChoices().forEach(System.out::println); + } + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertExileCount(playerA, "Swamp", chooseCardsCount); + assertLife(playerA, 20 + chooseCardsCount); + } + + protected void run_PlayerChoose_OnResolve(int chooseCardsCount, int availableCardsCount) { + // custom effect - select, exile and gain life + int minTarget = 0; + int maxTarget = 3; + String startingText = "select, exile and gain life"; + Ability ability = new SimpleActivatedAbility( + new SelectExileAndGainLifeCustomEffect(minTarget, maxTarget, Outcome.Benefit), + new ManaCostsImpl<>("") + ); + addCustomCardWithAbility("test choice", playerA, ability); + + addCard(Zone.HAND, playerA, "Swamp", availableCardsCount); + + checkHandCardCount("prepare", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Swamp", availableCardsCount); + checkExileCount("prepare", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Swamp", 0); + + checkPlayableAbility("can activate any time (even with zero cards)", 1, PhaseStep.PRECOMBAT_MAIN, playerA, startingText, true); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, startingText); + + if (chooseCardsCount > 0) { + // need selection + List targetCards = new ArrayList<>(); + IntStream.rangeClosed(1, chooseCardsCount).forEach(x -> { + targetCards.add("Swamp"); + }); + setChoice(playerA, String.join("^", targetCards)); + } else { + // need skip + // on 0 cards there are must be dialog with done button anyway + + // end selection: + // - x of 0 - yes + // - 1 of 3 - yes + // - 2 of 3 - yes + // - 3 of 3 - no, it's auto-finish on last select + // - 3 of 5 - no, it's auto-finish on last select + if (chooseCardsCount < maxTarget) { + setChoice(playerA, TestPlayer.CHOICE_SKIP); + } + + } + + if (DEBUG_ENABLE_DETAIL_LOGS) { + System.out.println("planning actions:"); + playerA.getActions().forEach(System.out::println); + System.out.println("planning targets:"); + playerA.getTargets().forEach(System.out::println); + System.out.println("planning choices:"); + playerA.getChoices().forEach(System.out::println); + } + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertExileCount(playerA, "Swamp", chooseCardsCount); + assertLife(playerA, 20 + chooseCardsCount); + } + + protected void run_PlayerChoose_OnResolve_AI(Outcome outcome, int minTargets, int maxTargets, int aiMustChooseCardsCount, int availableCardsCount) { + // custom effect - select, exile and gain life + String startingText = "{T}: Select, exile and gain life"; + Ability ability = new SimpleActivatedAbility( + new SelectExileAndGainLifeCustomEffect(minTargets, maxTargets, outcome), + new ManaCostsImpl<>("") + ); + ability.addCost(new TapSourceCost()); + addCustomCardWithAbility("test choice", playerA, ability); + + addCard(Zone.HAND, playerA, "Swamp", availableCardsCount); + addCard(Zone.HAND, playerA, "Forest", 1); + + // Forest play is workaround to disable lands play in ai priority + // {T} cost is workaround to disable multiple calls of the ability + playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Forest"); + + checkPlayableAbility("can activate any time (even with zero cards)", 1, PhaseStep.PRECOMBAT_MAIN, playerA, startingText, true); + + // AI must see bad effect and select only halves of the max targets, e.g. 1 of 3 + aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertExileCount(playerA, "Swamp", aiMustChooseCardsCount); + assertLife(playerA, 20 + aiMustChooseCardsCount); + } +} + +enum TotalTargetsValue implements DynamicValue { + instance; + + @Override + public TotalTargetsValue copy() { + return instance; + } + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return effect.getTargetPointer().getTargets(game, sourceAbility).size(); + } + + @Override + public String getMessage() { + return "total targets"; + } + + @Override + public String toString() { + return "X"; + } +} + +class SelectExileAndGainLifeCustomEffect extends OneShotEffect { + + private final int minTargets; + private final int maxTargets; + + SelectExileAndGainLifeCustomEffect(int minTargets, int maxTargets, Outcome outcome) { + super(outcome); + this.minTargets = minTargets; + this.maxTargets = maxTargets; + staticText = "select, exile and gain life"; + } + + private SelectExileAndGainLifeCustomEffect(final SelectExileAndGainLifeCustomEffect effect) { + super(effect); + this.minTargets = effect.minTargets; + this.maxTargets = effect.maxTargets; + } + + @Override + public SelectExileAndGainLifeCustomEffect copy() { + return new SelectExileAndGainLifeCustomEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + + Target target = new TargetCardInHand(this.minTargets, this.maxTargets, StaticFilters.FILTER_CARD).withNotTarget(true); + if (!player.choose(outcome, target, source, game)) { + return false; + } + + player.moveCardsToExile(new CardsImpl(target.getTargets()).getCards(game), source, game, false, source.getSourceId(), player.getLogName()); + player.gainLife(target.getTargets().size(), game, source); + return true; + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/targets/TargetsSelectionOnActivateTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/targets/TargetsSelectionOnActivateTest.java new file mode 100644 index 00000000000..6dde5d749b6 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/targets/TargetsSelectionOnActivateTest.java @@ -0,0 +1,89 @@ +package org.mage.test.cards.targets; + +import org.junit.Test; + +/** + * Testing targets selection on activate/cast (player.chooseTarget) + * + * @author JayDi85 + */ +public class TargetsSelectionOnActivateTest extends TargetsSelectionBaseTest { + + // no selects + + @Test + public void test_OnActivate_0_of_0() { + run_PlayerChooseTarget_OnActivate(0, 0); + } + + @Test + public void test_OnActivate_0_of_1() { + run_PlayerChooseTarget_OnActivate(0, 1); + } + + @Test + public void test_OnActivate_0_of_2() { + run_PlayerChooseTarget_OnActivate(0, 2); + } + + @Test + public void test_OnActivate_0_of_3() { + run_PlayerChooseTarget_OnActivate(0, 3); + } + + @Test + public void test_OnActivate_0_of_10() { + run_PlayerChooseTarget_OnActivate(0, 10); + } + + // 1 select + + @Test + public void test_OnActivate_1_of_1() { + run_PlayerChooseTarget_OnActivate(1, 1); + } + + @Test + public void test_OnActivate_1_of_2() { + run_PlayerChooseTarget_OnActivate(1, 2); + } + + @Test + public void test_OnActivate_1_of_3() { + run_PlayerChooseTarget_OnActivate(1, 3); + } + + @Test + public void test_OnActivate_1_of_10() { + run_PlayerChooseTarget_OnActivate(1, 10); + } + + // 2 selects + + @Test + public void test_OnActivate_2_of_2() { + run_PlayerChooseTarget_OnActivate(2, 2); + } + + @Test + public void test_OnActivate_2_of_3() { + run_PlayerChooseTarget_OnActivate(2, 3); + } + + @Test + public void test_OnActivate_2_of_10() { + run_PlayerChooseTarget_OnActivate(2, 10); + } + + // 3 selects + + @Test + public void test_OnActivate_3_of_3() { + run_PlayerChooseTarget_OnActivate(3, 3); + } + + @Test + public void test_OnActivate_3_of_10() { + run_PlayerChooseTarget_OnActivate(3, 10); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/targets/TargetsSelectionOnResolveAITest.java b/Mage.Tests/src/test/java/org/mage/test/cards/targets/TargetsSelectionOnResolveAITest.java new file mode 100644 index 00000000000..f5b39950bf4 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/targets/TargetsSelectionOnResolveAITest.java @@ -0,0 +1,66 @@ +package org.mage.test.cards.targets; + +import mage.constants.Outcome; +import org.junit.Test; + +/** + * Testing targets selection on resolve (player.choose) for AI + *

+ * AI must use logic like: + * - for good effects - choose as much as possible targets + * - for bad effects - choose as much as lower targets + * + * @author JayDi85 + */ +public class TargetsSelectionOnResolveAITest extends TargetsSelectionBaseTest { + + @Test + public void test_OnResolve_Good_0_of_0() { + run_PlayerChoose_OnResolve_AI(Outcome.Benefit, 0, 3, 0, 0); + } + + @Test + public void test_OnResolve_Good_1_of_1() { + run_PlayerChoose_OnResolve_AI(Outcome.Benefit, 0, 3, 1, 1); + } + + @Test + public void test_OnResolve_Good_2_of_2() { + run_PlayerChoose_OnResolve_AI(Outcome.Benefit, 0, 3, 2, 2); + } + + @Test + public void test_OnResolve_Good_3_of_3() { + run_PlayerChoose_OnResolve_AI(Outcome.Benefit, 0, 3, 3, 3); + } + + @Test + public void test_OnResolve_Good_3_of_10() { + run_PlayerChoose_OnResolve_AI(Outcome.Benefit, 0, 3, 3, 10); + } + + @Test + public void test_OnResolve_Bad_0_of_0() { + run_PlayerChoose_OnResolve_AI(Outcome.Detriment, 0, 3, 0, 0); + } + + @Test + public void test_OnResolve_Bad_0_of_1() { + run_PlayerChoose_OnResolve_AI(Outcome.Detriment, 0, 3, 0, 1); + } + + @Test + public void test_OnResolve_Bad_0_of_2() { + run_PlayerChoose_OnResolve_AI(Outcome.Detriment, 0, 3, 0, 2); + } + + @Test + public void test_OnResolve_Bad_0_of_3() { + run_PlayerChoose_OnResolve_AI(Outcome.Detriment, 0, 3, 0, 3); + } + + @Test + public void test_OnResolve_Bad_0_of_10() { + run_PlayerChoose_OnResolve_AI(Outcome.Detriment, 0, 3, 0, 10); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/targets/TargetsSelectionOnResolveTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/targets/TargetsSelectionOnResolveTest.java new file mode 100644 index 00000000000..5fe8e70d2d4 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/targets/TargetsSelectionOnResolveTest.java @@ -0,0 +1,91 @@ +package org.mage.test.cards.targets; + +import org.junit.Test; + +/** + * Testing targets selection on resolve (player.choose) + *

+ * Player can use any logic and choose any number of targets + * + * @author JayDi85 + */ +public class TargetsSelectionOnResolveTest extends TargetsSelectionBaseTest { + + // no selects + + @Test + public void test_OnActivate_0_of_0() { + run_PlayerChoose_OnResolve(0, 0); + } + + @Test + public void test_OnActivate_0_of_1() { + run_PlayerChoose_OnResolve(0, 1); + } + + @Test + public void test_OnActivate_0_of_2() { + run_PlayerChoose_OnResolve(0, 2); + } + + @Test + public void test_OnActivate_0_of_3() { + run_PlayerChoose_OnResolve(0, 3); + } + + @Test + public void test_OnActivate_0_of_10() { + run_PlayerChoose_OnResolve(0, 10); + } + + // 1 select + + @Test + public void test_OnActivate_1_of_1() { + run_PlayerChoose_OnResolve(1, 1); + } + + @Test + public void test_OnActivate_1_of_2() { + run_PlayerChoose_OnResolve(1, 2); + } + + @Test + public void test_OnActivate_1_of_3() { + run_PlayerChoose_OnResolve(1, 3); + } + + @Test + public void test_OnActivate_1_of_10() { + run_PlayerChoose_OnResolve(1, 10); + } + + // 2 selects + + @Test + public void test_OnActivate_2_of_2() { + run_PlayerChoose_OnResolve(2, 2); + } + + @Test + public void test_OnActivate_2_of_3() { + run_PlayerChoose_OnResolve(2, 3); + } + + @Test + public void test_OnActivate_2_of_10() { + run_PlayerChoose_OnResolve(2, 10); + } + + // 3 selects + + @Test + public void test_OnActivate_3_of_3() { + run_PlayerChoose_OnResolve(3, 3); + } + + @Test + public void test_OnActivate_3_of_10() { + run_PlayerChoose_OnResolve(3, 10); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EnterLeaveBattlefieldExileTargetTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EnterLeaveBattlefieldExileTargetTest.java index 0b1b861f318..f334f88dba2 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EnterLeaveBattlefieldExileTargetTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EnterLeaveBattlefieldExileTargetTest.java @@ -3,6 +3,7 @@ package org.mage.test.cards.triggers; 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; /** @@ -23,6 +24,7 @@ public class EnterLeaveBattlefieldExileTargetTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Angel of Serenity"); addTarget(playerA, "Silvercoat Lion^Pillarfield Ox"); + addTarget(playerA, TestPlayer.TARGET_SKIP); setChoice(playerA, true); setStrictChooseMode(true); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/SacredGroundTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/SacredGroundTest.java index 6e4aefc1cf7..ee676c58ac4 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/SacredGroundTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/SacredGroundTest.java @@ -144,7 +144,9 @@ public class SacredGroundTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Molten Rain", "Caves of Koilos"); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, 1); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Surgical Extraction", "Caves of Koilos"); + setChoice(playerA, true); // Pay 2 life instead of {B} + //setStrictChooseMode(true); TODO: good example of AI choices, so add new AI test instead non strict here setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/WorldgorgerDragonTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/WorldgorgerDragonTest.java index 3fbfb6678a6..648086e1d78 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/WorldgorgerDragonTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/WorldgorgerDragonTest.java @@ -7,7 +7,6 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * * @author LevelX2 */ public class WorldgorgerDragonTest extends CardTestPlayerBase { @@ -83,62 +82,48 @@ public class WorldgorgerDragonTest extends CardTestPlayerBase { // When Staunch Defenders enters the battlefield, you gain 4 life. addCard(Zone.BATTLEFIELD, playerA, "Staunch Defenders", 1); + // 1 cast and resolve castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Animate Dead", "Worldgorger Dragon"); + setChoice(playerA, "Worldgorger Dragon"); // attach + setChoice(playerA, "When {this} enters, if it's"); // x2 triggers (gain life from Staunch Defenders and etb from Animate Dead) + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}", 3); + + // 2 etb and attach setChoice(playerA, "Worldgorger Dragon"); setChoice(playerA, "When {this} enters, if it's"); - activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); - activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); - activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}", 3); + // 3 etb and attach setChoice(playerA, "Worldgorger Dragon"); setChoice(playerA, "When {this} enters, if it's"); - activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); - activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); - activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}", 3); + // 4 etb and attach setChoice(playerA, "Worldgorger Dragon"); setChoice(playerA, "When {this} enters, if it's"); - activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); - activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); - activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}", 3); + // 5 etb and attach + setChoice(playerA, "Worldgorger Dragon"); + setChoice(playerA, false); // no draws on infinite loop + setChoice(playerA, "When {this} enters, if it's"); + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}", 3); + + // 6 etb and attach setChoice(playerA, "Worldgorger Dragon"); setChoice(playerA, "When {this} enters, if it's"); - setChoice(playerA, false); - activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); - activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); - activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}", 3); - setChoice(playerA, "Worldgorger Dragon"); - setChoice(playerA, "When {this} enters, if it's"); + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}", 3 - 1); - activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); - activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); - activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); - - setChoice(playerA, "Worldgorger Dragon"); - setChoice(playerA, "When {this} enters, if it's"); - - activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); - activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); - activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); - - setChoice(playerA, "Worldgorger Dragon"); - setChoice(playerA, "When {this} enters, if it's"); - activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); - activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); - activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); - - activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); - activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Volcanic Geyser", playerB, 22); + // cast spell and stop infinite loop after 20+ mana in pool + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Volcanic Geyser", playerB, 20); setChoice(playerA, "X=20"); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); - assertLife(playerA, 44); + assertLife(playerA, 20 + 5 * 4); assertLife(playerB, 0); assertGraveyardCount(playerA, "Volcanic Geyser", 1); @@ -154,12 +139,11 @@ public class WorldgorgerDragonTest extends CardTestPlayerBase { * you choose to skip or pick a different creature, it always returns the * first creature you picked. Kind of hard to explain, but here's how to * reproduce: - * + *

* 1) Cast Animate Dead, targeting Worldgorger Dragon 2) Worldgorger Dragon * will exile Animate Dead, killing the dragon and returning the permanents * 3) Select Worldgorger again 4) Step 2 repeats 5) Attempt to select a * different creature. Worldgorger Dragon is returned instead. - * */ @Test public void testWithAnimateDeadDifferentTargets() { diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/delayed/PostMortemLungeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/delayed/PostMortemLungeTest.java index f2af0ab763c..6ea6014bd60 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/delayed/PostMortemLungeTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/delayed/PostMortemLungeTest.java @@ -29,6 +29,7 @@ public class PostMortemLungeTest extends CardTestPlayerBase { attack(1, playerA, "Elite Vanguard"); + setStrictChooseMode(false); // TODO: good test for AI's announceX - duplicate it as AI test setStopAt(1, PhaseStep.CLEANUP); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/VengefulTownsfolkTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/VengefulTownsfolkTest.java index 9f63da3acc7..2e36053fdb4 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/VengefulTownsfolkTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/VengefulTownsfolkTest.java @@ -3,6 +3,7 @@ package org.mage.test.cards.triggers.dies; 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; /** @@ -159,6 +160,7 @@ public class VengefulTownsfolkTest extends CardTestPlayerBase { activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}, Sacrifice up to three"); setChoice(playerA, "Angel of the God-Pharaoh"); // sac cost + setChoice(playerA, TestPlayer.CHOICE_SKIP); setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); @@ -183,6 +185,7 @@ public class VengefulTownsfolkTest extends CardTestPlayerBase { activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}, Sacrifice up to three"); setChoice(playerA, "Grizzly Bears^Angel of the God-Pharaoh"); // sac cost + setChoice(playerA, TestPlayer.CHOICE_SKIP); setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); 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 2238c6dffeb..e79cc0bc719 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 @@ -6,7 +6,6 @@ import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.AlternativeSourceCosts; import mage.abilities.costs.Cost; import mage.abilities.costs.Costs; -import mage.abilities.costs.VariableCost; import mage.abilities.costs.mana.ManaCost; import mage.abilities.costs.mana.ManaCosts; import mage.abilities.effects.common.InfoEffect; @@ -70,7 +69,7 @@ public class TestPlayer implements Player { private static final Logger LOGGER = Logger.getLogger(TestPlayer.class); - private static final int takeMaxTargetsPerChoose = Integer.MAX_VALUE; // TODO: set 1, fix broken tests and replace all "for (String targetDefinition" by targets.get(0) + private static final int takeMaxTargetsPerChoose = Integer.MAX_VALUE; // TODO: set 1 here, fix broken tests and replace all "for (String targetDefinition" by targets.get(0) public static final String TARGET_SKIP = "[target_skip]"; // stop/skip targeting public static final String CHOICE_SKIP = "[choice_skip]"; // stop/skip choice @@ -113,7 +112,7 @@ public class TestPlayer implements Player { // (example: card call TestPlayer's choice, but it uses another choices, see docs in TestComputerPlayer) private boolean strictChooseMode = false; - private String[] groupsForTargetHandling = null; + private String[] groupsForTargetHandling = null; // predefined targets list from cast/activate command // Tracks the initial turns (turn 0s) both players are given at the start of the game. // Before actual turns start. Needed for checking attacker/blocker legality in the tests @@ -516,7 +515,7 @@ public class TestPlayer implements Player { } } for (UUID id : currentTarget.possibleTargets(ability.getControllerId(), ability, game)) { - if (!currentTarget.getTargets().contains(id)) { + if (!currentTarget.contains(id)) { MageObject object = game.getObject(id); if (object == null) { @@ -539,7 +538,7 @@ public class TestPlayer implements Player { } // found, can use as target - if (currentTarget.getNumberOfTargets() == 1) { + if (currentTarget.getMinNumberOfTargets() == 1) { currentTarget.clearChosen(); } if (currentTarget.getOriginalTarget() instanceof TargetCreaturePermanentAmount) { @@ -594,7 +593,7 @@ public class TestPlayer implements Player { } // fake test ability for triggers and events - Ability source = new SimpleStaticAbility(Zone.OUTSIDE, new InfoEffect("adding testing cards")); + Ability source = new SimpleStaticAbility(Zone.OUTSIDE, new InfoEffect("fake ability")); source.setControllerId(this.getId()); int numberOfActions = actions.size(); @@ -2049,7 +2048,8 @@ public class TestPlayer implements Player { int numBlocked = blocked.size(); // Can't block any more creatures - if (++numBlocked > blocker.getMaxBlocks()) { + // maxBlocks = 0 equals to "can block any number of creatures" + if (blocker.getMaxBlocks() > 0 && ++numBlocked > blocker.getMaxBlocks()) { return false; } @@ -2098,8 +2098,18 @@ public class TestPlayer implements Player { return "Ability: null"; } - private String getInfo(Target o, Game game) { - return "Target: " + (o != null ? o.getClass().getSimpleName() + ": " + o.getMessage(game) : "null"); + private String getInfo(Target target, Ability source, Game game) { + if (target == null) { + return "Target: null"; + } + UUID abilityControllerId = getId(); + if (target.getTargetController() != null && target.getAbilityController() != null) { + abilityControllerId = target.getAbilityController(); + } + Set possibleTargets = target.possibleTargets(abilityControllerId, source, game); + + return "Target: selected " + target.getSize() + ", possible " + possibleTargets.size() + + ", " + target.getClass().getSimpleName() + ": " + target.getMessage(game); } private void assertAliasSupportInChoices(boolean methodSupportAliases) { @@ -2210,7 +2220,7 @@ public class TestPlayer implements Player { // skip choices if (possibleChoice.equals(CHOICE_SKIP)) { choices.remove(0); - return true; + return false; // false - stop to choose } if (choice.setChoiceByAnswers(choices, true)) { @@ -2263,27 +2273,28 @@ public class TestPlayer implements Player { abilityControllerId = target.getAbilityController(); } + // TODO: warning, some cards call player.choose methods instead target.choose, see #8254 + // most use cases - discard and other cost with choice like that method + // must migrate all choices.remove(xxx) to choices.remove(0), takeMaxTargetsPerChoose can help to find it + // ignore player select if (target.getMessage(game).equals("Select a starting player")) { return computerPlayer.choose(outcome, target, source, game, options); } + boolean isAddedSomething = false; // must return true on any changes in targets, so game can ask next choose dialog until finish + assertAliasSupportInChoices(true); if (!choices.isEmpty()) { // skip choices - if (choices.get(0).equals(CHOICE_SKIP)) { + if (tryToSkipSelection(choices, CHOICE_SKIP)) { Assert.assertTrue("found skip choice, but it require more choices, needs " + (target.getMinNumberOfTargets() - target.getTargets().size()) + " more", target.getTargets().size() >= target.getMinNumberOfTargets()); - choices.remove(0); - return true; + return false; // false - stop to choose } - List usedChoices = new ArrayList<>(); - List usedTargets = new ArrayList<>(); - - // TODO: Allow to choose a player with TargetPermanentOrPlayer if ((target.getOriginalTarget() instanceof TargetPermanent) || (target.getOriginalTarget() instanceof TargetPermanentOrPlayer)) { // player target not implemented yet @@ -2293,10 +2304,13 @@ public class TestPlayer implements Player { } else { filterPermanent = ((TargetPermanent) target.getOriginalTarget()).getFilter(); } - while (!choices.isEmpty()) { + while (!choices.isEmpty()) { // TODO: remove cycle after main commits + if (tryToSkipSelection(choices, CHOICE_SKIP)) { + return false; // stop dialog + } String choiceRecord = choices.get(0); String[] targetList = choiceRecord.split("\\^"); - boolean targetFound = false; + isAddedSomething = false; for (String targetName : targetList) { boolean originOnly = false; boolean copyOnly = false; @@ -2311,14 +2325,14 @@ public class TestPlayer implements Player { } } for (Permanent permanent : game.getBattlefield().getActivePermanents(filterPermanent, abilityControllerId, source, game)) { - if (target.getTargets().contains(permanent.getId())) { + if (target.contains(permanent.getId())) { continue; } if (hasObjectTargetNameOrAlias(permanent, targetName)) { if (target.isNotTarget() || target.canTarget(abilityControllerId, permanent.getId(), source, game)) { if ((permanent.isCopy() && !originOnly) || (!permanent.isCopy() && !copyOnly)) { target.add(permanent.getId(), game); - targetFound = true; + isAddedSomething = true; break; } } @@ -2326,7 +2340,7 @@ public class TestPlayer implements Player { if (target.isNotTarget() || target.canTarget(abilityControllerId, permanent.getId(), source, game)) { if ((permanent.isCopy() && !originOnly) || (!permanent.isCopy() && !copyOnly)) { target.add(permanent.getId(), game); - targetFound = true; + isAddedSomething = true; break; } } @@ -2335,45 +2349,49 @@ public class TestPlayer implements Player { } try { - if (target.isChosen(game)) { - return true; - } else { - if (!targetFound) { - failOnLastBadChoice(game, source, target, choiceRecord, "unknown or can't target"); + if (isAddedSomething) { + if (target.isChoiceCompleted(abilityControllerId, source, game)) { + return true; } + } else { + failOnLastBadChoice(game, source, target, choiceRecord, "invalid target or miss skip command"); } } finally { choices.remove(0); } - } + return isAddedSomething; + } // choices } if (target instanceof TargetPlayer) { - while (!choices.isEmpty()) { + while (!choices.isEmpty()) { // TODO: remove cycle after main commits + if (tryToSkipSelection(choices, CHOICE_SKIP)) { + return false; // stop dialog + } String choiceRecord = choices.get(0); - boolean targetFound = false; + isAddedSomething = false; for (Player player : game.getPlayers().values()) { if (player.getName().equals(choiceRecord)) { - if (target.canTarget(abilityControllerId, player.getId(), null, game) && !target.getTargets().contains(player.getId())) { + if (target.canTarget(abilityControllerId, player.getId(), null, game) && !target.contains(player.getId())) { target.add(player.getId(), game); - targetFound = true; - } else { - failOnLastBadChoice(game, source, target, choiceRecord, "can't target"); + isAddedSomething = true; } } } try { - if (target.isChosen(game)) { - return true; - } - if (!targetFound) { - failOnLastBadChoice(game, source, target, choiceRecord, "unknown target"); + if (isAddedSomething) { + if (target.isChoiceCompleted(abilityControllerId, source, game)) { + return true; + } + } else { + failOnLastBadChoice(game, source, target, choiceRecord, "invalid target or miss skip command"); } } finally { choices.remove(0); } - } + return isAddedSomething; + } // while choices } // TODO: add same choices fixes for other target types (one choice must uses only one time for one target) @@ -2382,102 +2400,78 @@ public class TestPlayer implements Player { // only unique targets //TargetCard targetFull = ((TargetCard) target); - usedChoices.clear(); - usedTargets.clear(); - boolean targetCompleted = false; - - CheckAllChoices: - for (int choiceIndex = 0; choiceIndex < choices.size(); choiceIndex++) { - String choiceRecord = choices.get(choiceIndex); - if (targetCompleted) { - break CheckAllChoices; + for (String choiceRecord : new ArrayList<>(choices)) { // TODO: remove cycle after main commits + if (tryToSkipSelection(choices, CHOICE_SKIP)) { + return false; // stop dialog } - - boolean targetFound = false; + isAddedSomething = false; String[] possibleChoices = choiceRecord.split("\\^"); - CheckOneChoice: for (String possibleChoice : possibleChoices) { Set possibleCards = target.possibleTargets(abilityControllerId, source, game); - CheckTargetsList: for (UUID targetId : possibleCards) { MageObject targetObject = game.getCard(targetId); if (hasObjectTargetNameOrAlias(targetObject, possibleChoice)) { - if (target.canTarget(targetObject.getId(), game)) { - // only unique targets - if (usedTargets.contains(targetObject.getId())) { - continue; - } - - // OK, can use it + if (target.canTarget(targetObject.getId(), game) && !target.contains(targetObject.getId())) { target.add(targetObject.getId(), game); - targetFound = true; - usedTargets.add(targetObject.getId()); - - // break on full targets list - if (target.getTargets().size() >= target.getMaxNumberOfTargets()) { - targetCompleted = true; - break CheckOneChoice; - } - - // restart search - break CheckTargetsList; + isAddedSomething = true; + break; } } } } - - if (targetFound) { - usedChoices.add(choiceIndex); - } - } - - // apply only on ALL targets or revert - if (usedChoices.size() > 0) { - if (target.isChosen(game)) { - // remove all used choices - for (int i = choices.size(); i >= 0; i--) { - if (usedChoices.contains(i)) { - choices.remove(i); + try { + if (isAddedSomething) { + if (target.isChoiceCompleted(abilityControllerId, source, game)) { + return true; } + } else { + failOnLastBadChoice(game, source, target, choiceRecord, "invalid target or miss skip command"); } - return true; - } else { - Assert.fail("Not full targets list."); - target.clearChosen(); + } finally { + choices.remove(0); } - } + return isAddedSomething; + } // for choices } if (target.getOriginalTarget() instanceof TargetSource) { - Set possibleTargets; TargetSource t = ((TargetSource) target.getOriginalTarget()); - possibleTargets = t.possibleTargets(abilityControllerId, source, game); - for (String choiceRecord : choices) { + Set possibleTargets = t.possibleTargets(abilityControllerId, source, game); + // TODO: enable choices.get first instead all + for (String choiceRecord : new ArrayList<>(choices)) { // TODO: remove cycle after main commits + if (tryToSkipSelection(choices, CHOICE_SKIP)) { + return false; // stop dialog + } String[] targetList = choiceRecord.split("\\^"); - boolean targetFound = false; + isAddedSomething = false; for (String targetName : targetList) { for (UUID targetId : possibleTargets) { MageObject targetObject = game.getObject(targetId); if (targetObject != null) { if (hasObjectTargetNameOrAlias(targetObject, targetName)) { - List alreadyTargetted = target.getTargets(); - if (t.canTarget(targetObject.getId(), game)) { - if (alreadyTargetted != null && !alreadyTargetted.contains(targetObject.getId())) { - target.add(targetObject.getId(), game); - choices.remove(choiceRecord); - targetFound = true; - } + if (t.canTarget(targetObject.getId(), game) && !target.contains(targetObject.getId())) { + target.add(targetObject.getId(), game); + isAddedSomething = true; + break; } } } - if (targetFound) { - choices.remove(choiceRecord); - return true; - } } } - } + try { + if (isAddedSomething) { + if (target.isChoiceCompleted(abilityControllerId, source, game)) { + return true; + } + } else { + failOnLastBadChoice(game, source, target, choiceRecord, "invalid target or miss skip command"); + } + } finally { + choices.remove(choiceRecord); + } + return isAddedSomething; + } // for choices } // TODO: enable fail checks and fix tests @@ -2486,7 +2480,7 @@ public class TestPlayer implements Player { } } - this.chooseStrictModeFailed("choice", game, getInfo(source, game) + "\n" + getInfo(target, game)); + this.chooseStrictModeFailed("choice", game, getInfo(source, game) + "\n" + getInfo(target, source, game)); return computerPlayer.choose(outcome, target, source, game, options); } @@ -2529,7 +2523,7 @@ public class TestPlayer implements Player { + (target.getMinNumberOfTargets() - target.getTargets().size()) + " more", target.getTargets().size() >= target.getMinNumberOfTargets()); targets.remove(0); - return true; + return false; // false - stop to choose } Set targetCardZonesChecked = new HashSet<>(); // control miss implementation @@ -2588,7 +2582,7 @@ public class TestPlayer implements Player { } for (Permanent permanent : game.getBattlefield().getActivePermanents((FilterPermanent) filter, abilityControllerId, source, game)) { if (hasObjectTargetNameOrAlias(permanent, targetName) || (permanent.getName() + '-' + permanent.getExpansionSetCode()).equals(targetName)) { // TODO: remove exp code search? - if (target.canTarget(abilityControllerId, permanent.getId(), source, game) && !target.getTargets().contains(permanent.getId())) { + if (target.canTarget(abilityControllerId, permanent.getId(), source, game) && !target.contains(permanent.getId())) { if ((permanent.isCopy() && !originOnly) || (!permanent.isCopy() && !copyOnly)) { target.addTarget(permanent.getId(), source, game); targetFound = true; @@ -2618,7 +2612,7 @@ public class TestPlayer implements Player { for (String targetName : targetList) { for (Card card : computerPlayer.getHand().getCards(((TargetCard) target.getOriginalTarget()).getFilter(), game)) { if (hasObjectTargetNameOrAlias(card, targetName) || (card.getName() + '-' + card.getExpansionSetCode()).equals(targetName)) { // TODO: remove set code search? - if (target.canTarget(abilityControllerId, card.getId(), source, game) && !target.getTargets().contains(card.getId())) { + if (target.canTarget(abilityControllerId, card.getId(), source, game) && !target.contains(card.getId())) { target.addTarget(card.getId(), source, game); targetFound = true; break; // return to next targetName @@ -2656,7 +2650,7 @@ public class TestPlayer implements Player { for (String targetName : targetList) { for (Card card : game.getExile().getCards(filter, game)) { if (hasObjectTargetNameOrAlias(card, targetName) || (card.getName() + '-' + card.getExpansionSetCode()).equals(targetName)) { // TODO: remove set code search? - if (target.canTarget(abilityControllerId, card.getId(), source, game) && !target.getTargets().contains(card.getId())) { + if (target.canTarget(abilityControllerId, card.getId(), source, game) && !target.contains(card.getId())) { target.addTarget(card.getId(), source, game); targetFound = true; break; // return to next targetName @@ -2681,7 +2675,7 @@ public class TestPlayer implements Player { for (String targetName : targetList) { for (Card card : game.getBattlefield().getAllActivePermanents()) { if (hasObjectTargetNameOrAlias(card, targetName) || (card.getName() + '-' + card.getExpansionSetCode()).equals(targetName)) { // TODO: remove set code search? - if (targetFull.canTarget(abilityControllerId, card.getId(), source, game) && !targetFull.getTargets().contains(card.getId())) { + if (targetFull.canTarget(abilityControllerId, card.getId(), source, game) && !targetFull.contains(card.getId())) { targetFull.add(card.getId(), game); targetFound = true; break; // return to next targetName @@ -2733,7 +2727,7 @@ public class TestPlayer implements Player { Player player = game.getPlayer(playerId); for (Card card : player.getGraveyard().getCards(targetFull.getFilter(), game)) { if (hasObjectTargetNameOrAlias(card, targetName) || (card.getName() + '-' + card.getExpansionSetCode()).equals(targetName)) { // TODO: remove set code search? - if (target.canTarget(abilityControllerId, card.getId(), source, game) && !target.getTargets().contains(card.getId())) { + if (target.canTarget(abilityControllerId, card.getId(), source, game) && !target.contains(card.getId())) { target.addTarget(card.getId(), source, game); targetFound = true; break IterateGraveyards; // return to next targetName @@ -2748,7 +2742,6 @@ public class TestPlayer implements Player { return true; } } - } // stack @@ -2763,7 +2756,7 @@ public class TestPlayer implements Player { for (String targetName : targetList) { for (StackObject stackObject : game.getStack()) { if (hasObjectTargetNameOrAlias(stackObject, targetName)) { - if (target.canTarget(abilityControllerId, stackObject.getId(), source, game) && !target.getTargets().contains(stackObject.getId())) { + if (target.canTarget(abilityControllerId, stackObject.getId(), source, game) && !target.contains(stackObject.getId())) { target.addTarget(stackObject.getId(), source, game); targetFound = true; break; // return to next targetName @@ -2798,24 +2791,25 @@ public class TestPlayer implements Player { // how to fix: implement target class processing above (if it a permanent target then check "filter instanceof" code too) if (!targets.isEmpty()) { String message; + Set possibleTargets = target.possibleTargets(abilityControllerId, source, game); if (source != null) { message = this.getName() + " - Targets list was setup by addTarget with " + targets + ", but not used" + "\nCard: " + source.getSourceObject(game) + "\nAbility: " + source.getClass().getSimpleName() + " (" + source.getRule() + ")" - + "\nTarget: " + target.getClass().getSimpleName() + " (" + target.getMessage(game) + ")" + + "\nTarget: selected " + target.getSize() + ", possible " + possibleTargets.size() + ", " + target.getClass().getSimpleName() + " (" + target.getMessage(game) + ")" + "\nYou must implement target class support in TestPlayer, \"filter instanceof\", or setup good targets"; } else { message = this.getName() + " - Targets list was setup by addTarget with " + targets + ", but not used" + "\nCard: unknown source" + "\nAbility: unknown source" - + "\nTarget: " + target.getClass().getSimpleName() + " (" + target.getMessage(game) + ")" + + "\nTarget: selected " + target.getSize() + ", possible " + possibleTargets.size() + ", " + target.getClass().getSimpleName() + " (" + target.getMessage(game) + ")" + "\nYou must implement target class support in TestPlayer, \"filter instanceof\", or setup good targets"; } Assert.fail(message); } - this.chooseStrictModeFailed("target", game, getInfo(source, game) + "\n" + getInfo(target, game)); + this.chooseStrictModeFailed("target", game, getInfo(source, game) + "\n" + getInfo(target, source, game)); return computerPlayer.chooseTarget(outcome, target, source, game); } @@ -2834,7 +2828,7 @@ public class TestPlayer implements Player { + (target.getMinNumberOfTargets() - target.getTargets().size()) + " more", target.getTargets().size() >= target.getMinNumberOfTargets()); targets.remove(0); - return true; + return false; // false - stop to choose } for (String targetDefinition : targets.stream().limit(takeMaxTargetsPerChoose).collect(Collectors.toList())) { String[] targetList = targetDefinition.split("\\^"); @@ -2842,7 +2836,7 @@ public class TestPlayer implements Player { for (String targetName : targetList) { for (Card card : cards.getCards(game)) { if (hasObjectTargetNameOrAlias(card, targetName) - && !target.getTargets().contains(card.getId()) + && !target.contains(card.getId()) && target.canTarget(abilityControllerId, card.getId(), source, cards, game)) { target.addTarget(card.getId(), source, game); targetFound = true; @@ -2861,7 +2855,7 @@ public class TestPlayer implements Player { LOGGER.warn("Wrong target"); } - this.chooseStrictModeFailed("target", game, getInfo(source, game) + "\n" + getInfo(target, game)); + this.chooseStrictModeFailed("target", game, getInfo(source, game) + "\n" + getInfo(target, source, game)); return computerPlayer.chooseTarget(outcome, cards, target, source, game); } @@ -2921,39 +2915,26 @@ public class TestPlayer implements Player { } @Override - public int announceXMana(int min, int max, String message, Game game, Ability ability) { + public int announceX(int min, int max, String message, Game game, Ability source, boolean isManaPay) { assertAliasSupportInChoices(false); - if (!choices.isEmpty()) { - for (String choice : choices) { - if (choice.startsWith("X=")) { - int xValue = Integer.parseInt(choice.substring(2)); - assertXMinMaxValue(game, ability, xValue, min, max); - choices.remove(choice); - return xValue; - } - } + + // fast calc on nothing to choose + if (min >= max) { + return min; } - this.chooseStrictModeFailed("choice", game, getInfo(ability, game) - + "\nMessage: " + message + prepareXMaxInfo(min, max)); - return computerPlayer.announceXMana(min, max, message, game, ability); - } - - @Override - public int announceXCost(int min, int max, String message, Game game, Ability ability, VariableCost variablCost) { - assertAliasSupportInChoices(false); if (!choices.isEmpty()) { if (choices.get(0).startsWith("X=")) { int xValue = Integer.parseInt(choices.get(0).substring(2)); - assertXMinMaxValue(game, ability, xValue, min, max); + assertXMinMaxValue(game, source, xValue, min, max); choices.remove(0); return xValue; } } - this.chooseStrictModeFailed("choice", game, getInfo(ability, game) + this.chooseStrictModeFailed("choice", game, getInfo(source, game) + "\nMessage: " + message + prepareXMaxInfo(min, max)); - return computerPlayer.announceXCost(min, max, message, game, ability, null); + return computerPlayer.announceX(min, max, message, game, source, isManaPay); } private String prepareXMaxInfo(int min, int max) { @@ -2974,8 +2955,14 @@ public class TestPlayer implements Player { } @Override - public int getAmount(int min, int max, String message, Game game) { + public int getAmount(int min, int max, String message, Ability source, Game game) { assertAliasSupportInChoices(false); + + // fast calc on nothing to choose + if (min >= max) { + return min; + } + if (!choices.isEmpty()) { if (choices.get(0).startsWith("X=")) { int xValue = Integer.parseInt(choices.get(0).substring(2)); @@ -2985,7 +2972,7 @@ public class TestPlayer implements Player { } this.chooseStrictModeFailed("choice", game, message); - return computerPlayer.getAmount(min, max, message, game); + return computerPlayer.getAmount(min, max, message, source, game); } @Override @@ -4270,32 +4257,40 @@ public class TestPlayer implements Player { return choose(outcome, target, source, game, null); } + private boolean tryToSkipSelection(List selections, String selectionMark) { + if (!selections.isEmpty() && selections.get(0).equals(selectionMark)) { + selections.remove(0); + return true; + } + return false; + } + @Override public boolean choose(Outcome outcome, Cards cards, TargetCard target, Ability source, Game game) { assertAliasSupportInChoices(false); if (!choices.isEmpty()) { // skip choices - if (choices.get(0).equals(CHOICE_SKIP)) { - choices.remove(0); + if (tryToSkipSelection(choices, CHOICE_SKIP)) { if (cards.isEmpty()) { // cancel button forced in GUI on no possible choices + // TODO: need research return false; } else { Assert.assertTrue("found skip choice, but it require more choices, needs " + (target.getMinNumberOfTargets() - target.getTargets().size()) + " more", target.getTargets().size() >= target.getMinNumberOfTargets()); - return true; + return false; // stop dialog } } - for (String choose2 : choices) { + for (String choose2 : new ArrayList<>(choices)) { // TODO: More targetting to fix String[] targetList = choose2.split("\\^"); boolean targetFound = false; for (String targetName : targetList) { for (Card card : cards.getCards(game)) { - if (target.getTargets().contains(card.getId())) { + if (target.contains(card.getId())) { continue; } if (hasObjectTargetNameOrAlias(card, targetName)) { @@ -4316,7 +4311,7 @@ public class TestPlayer implements Player { assertWrongChoiceUsage(choices.size() > 0 ? choices.get(0) : "empty list"); } - this.chooseStrictModeFailed("choice", game, getInfo(source, game) + "\n" + getInfo(target, game)); + this.chooseStrictModeFailed("choice", game, getInfo(source, game) + "\n" + getInfo(target, source, game)); return computerPlayer.choose(outcome, cards, target, source, game); } @@ -4338,7 +4333,7 @@ public class TestPlayer implements Player { + (target.getMinNumberOfTargets() - target.getTargets().size()) + " more", target.getTargets().size() >= target.getMinNumberOfTargets()); targets.remove(0); - return false; // false in chooseTargetAmount = stop to choose + return false; // false - stop to choose } // only target amount needs @@ -4378,7 +4373,7 @@ public class TestPlayer implements Player { } if (foundTarget) { - if (!target.getTargets().contains(possibleTarget) && target.canTarget(possibleTarget, source, game)) { + if (!target.contains(possibleTarget) && target.canTarget(possibleTarget, source, game)) { // can select target.addTarget(possibleTarget, targetAmount, source, game); targets.remove(0); @@ -4389,7 +4384,7 @@ public class TestPlayer implements Player { } } - this.chooseStrictModeFailed("target", game, getInfo(source, game) + "\n" + getInfo(target, game)); + this.chooseStrictModeFailed("target", game, getInfo(source, game) + "\n" + getInfo(target, source, game)); return computerPlayer.chooseTargetAmount(outcome, target, source, game); } @@ -4738,7 +4733,7 @@ public class TestPlayer implements Player { Assert.fail(String.format("Found wrong choice command (%s):\n%s\n%s\n%s", reason, lastChoice, - getInfo(target, game), + getInfo(target, source, game), getInfo(source, game) )); } 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 228e51880ab..014e2fb865e 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 @@ -6,7 +6,6 @@ import mage.ObjectColor; import mage.abilities.Ability; import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.ContinuousEffectsList; -import mage.abilities.effects.Effect; import mage.cards.Card; import mage.cards.decks.Deck; import mage.cards.decks.DeckCardLists; @@ -32,7 +31,6 @@ import mage.players.Player; import mage.server.game.GameSessionPlayer; import mage.util.CardUtil; import mage.util.ThreadUtils; -import mage.utils.StreamUtils; import mage.utils.SystemUtil; import mage.view.GameView; import org.junit.Assert; @@ -1677,7 +1675,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement public void assertChoicesCount(TestPlayer player, int count) throws AssertionError { String mes = String.format( - "(Choices of %s) Count are not equal (found %s). Some inner choose dialogs can be set up only in strict mode.", + "(Choices of %s) Count are not equal (found %s). Make sure you use target.chooseXXX instead player.choose. Also some inner choose dialogs can be set up only in strict mode.", player.getName(), player.getChoices() ); @@ -1686,7 +1684,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement public void assertTargetsCount(TestPlayer player, int count) throws AssertionError { String mes = String.format( - "(Targets of %s) Count are not equal (found %s). Some inner choose dialogs can be set up only in strict mode.", + "(Targets of %s) Count are not equal (found %s). Make sure you use target.chooseXXX instead player.choose. Also some inner choose dialogs can be set up only in strict mode.", player.getName(), player.getTargets() ); @@ -2271,11 +2269,18 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement setChoice(player, choice ? "Yes" : "No", timesToChoose); } + /** + * Declare non target choice. You can use multiple choices in one line like setChoice(name1^name2) + * Also support "up to" choices, e.g. choose 2 of 3 cards by setChoice(card1^card2) + setChoice(TestPlayer.CHOICE_SKIP) + */ public void setChoice(TestPlayer player, String choice) { setChoice(player, choice, 1); } public void setChoice(TestPlayer player, String choice, int timesToChoose) { + if (choice.equals(TestPlayer.TARGET_SKIP)) { + Assert.fail("setChoice allow only TestPlayer.CHOICE_SKIP, but found " + choice); + } for (int i = 0; i < timesToChoose; i++) { player.addChoice(choice); } @@ -2340,13 +2345,18 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * * @param player * @param target you can add multiple targets by separating them by the "^" - * character e.g. "creatureName1^creatureName2" you can - * qualify the target additional by setcode e.g. + * character e.g. "creatureName1^creatureName2" + * - + * you can qualify the target additional by setcode e.g. * "creatureName-M15" you can add [no copy] to the end of the * target name to prohibit targets that are copied you can add * [only copy] to the end of the target name to allow only * targets that are copies. For modal spells use a prefix with * the mode number: mode=1Lightning Bolt^mode=2Silvercoat Lion + * - + * it's also support multiple addTarget commands instead single line, + * so you can declare not full "up to" targets list by addTarget(name) + * and addTarget(TestPlayer.TARGET_SKIP) */ // TODO: mode options doesn't work here (see BrutalExpulsionTest) public void addTarget(TestPlayer player, String target) { @@ -2354,6 +2364,10 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement } public void addTarget(TestPlayer player, String target, int timesToChoose) { + if (target.equals(TestPlayer.CHOICE_SKIP)) { + Assert.fail("addTarget allow only TestPlayer.TARGET_SKIP, but found " + target); + } + for (int i = 0; i < timesToChoose; i++) { assertAliaseSupportInActivateCommand(target, true); player.addTarget(target); diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/cheats/LoadCheatsTest.java b/Mage.Tests/src/test/java/org/mage/test/serverside/cheats/LoadCheatsTest.java index 1a7d63ed24d..1bf82497179 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/cheats/LoadCheatsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/cheats/LoadCheatsTest.java @@ -55,7 +55,7 @@ public class LoadCheatsTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); - setChoice(playerA, "7"); // choose [group 3]: 7 = 4 default menus + 3 group + setChoice(playerA, "8"); // choose [group 3]: 8 = 5 default menus + 3 group SystemUtil.executeCheatCommands(currentGame, commandsFile, playerA); assertHandCount(playerA, "Razorclaw Bear", 1); diff --git a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java index 7c0089cf504..e8563d8e838 100644 --- a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java +++ b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java @@ -70,7 +70,7 @@ public class VerifyCardDataTest { private static final Logger logger = Logger.getLogger(VerifyCardDataTest.class); - private static final String FULL_ABILITIES_CHECK_SET_CODES = "DFT"; // check ability text due mtgjson, can use multiple sets like MAT;CMD or * for all + private static final String FULL_ABILITIES_CHECK_SET_CODES = "WHO"; // check ability text due mtgjson, can use multiple sets like MAT;CMD or * for all private static final boolean CHECK_ONLY_ABILITIES_TEXT = false; // use when checking text locally, suppresses unnecessary checks and output messages private static final boolean CHECK_COPYABLE_FIELDS = true; // disable for better verify test performance @@ -307,6 +307,9 @@ public class VerifyCardDataTest { check(((CardWithHalves) card).getLeftHalfCard(), cardIndex); check(((CardWithHalves) card).getRightHalfCard(), cardIndex); } else if (card instanceof CardWithSpellOption) { + if (card.isLand()) { // temporary until scryfall fixes the mana cost issue for adventure lands + continue; + } check(card, cardIndex); check(((CardWithSpellOption) card).getSpellCard(), cardIndex); } else { @@ -2132,7 +2135,7 @@ public class VerifyCardDataTest { boolean isPutToGraveAbility = rules.contains("put into") && rules.contains("graveyard") && rules.contains("from the battlefield"); - boolean isLeavesBattlefield = rules.contains("leaves the battlefield"); + boolean isLeavesBattlefield = rules.contains("leaves the battlefield") && !rules.contains("until {this} leaves the battlefield"); if (triggeredAbility.isLeavesTheBattlefieldTrigger()) { // TODO: add check for wrongly enabled settings too? } else { diff --git a/Mage/src/main/java/mage/MageIdentifier.java b/Mage/src/main/java/mage/MageIdentifier.java index 923c6ead139..fd60b2e5524 100644 --- a/Mage/src/main/java/mage/MageIdentifier.java +++ b/Mage/src/main/java/mage/MageIdentifier.java @@ -60,6 +60,7 @@ public enum MageIdentifier { DemonicEmbraceAlternateCast, FalcoSparaPactweaverAlternateCast, HelbruteAlternateCast, + IntoThePitAlternateCast, MaestrosAscendencyAlternateCast, NashiMoonSagesScionAlternateCast, OsteomancerAdeptAlternateCast, @@ -78,7 +79,8 @@ public enum MageIdentifier { FiresOfMountDoomAlternateCast, PrimalPrayersAlternateCast, QuilledGreatwurmAlternateCast, - WickerfolkIndomitableAlternateCast; + WickerfolkIndomitableAlternateCast, + ValgavothTerrorEaterAlternateCast; /** * Additional text if there is need to differentiate two very similar effects diff --git a/Mage/src/main/java/mage/abilities/AbilitiesImpl.java b/Mage/src/main/java/mage/abilities/AbilitiesImpl.java index 87758f7fc21..71083f3b129 100644 --- a/Mage/src/main/java/mage/abilities/AbilitiesImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilitiesImpl.java @@ -92,7 +92,12 @@ public class AbilitiesImpl extends ArrayList implements Ab String rule = ability.getRule(); if (rule != null) { if (!rule.isEmpty()) { - rules.add(Character.toUpperCase(rule.charAt(0)) + rule.substring(1)); + rule = Character.toUpperCase(rule.charAt(0)) + rule.substring(1); + if (ability.getRuleAtTheTop()) { + rules.add(0, rule); + } else { + rules.add(rule); + } } } else { // logging so we can still can be made aware of rule problems a card has String cardName = ((SpellAbility) ability).getCardName(); diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index 4a01534e11b..1b8223083ba 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -787,8 +787,8 @@ public abstract class AbilityImpl implements Ability { xValue = variableManaCost.getAmount(); } else { // announce by player - xValue = controller.announceXMana(variableManaCost.getMinX(), variableManaCost.getMaxX(), - "Announce the value for " + variableManaCost.getText(), game, this); + xValue = controller.announceX(variableManaCost.getMinX(), variableManaCost.getMaxX(), + "Announce the value for " + variableManaCost.getText(), game, this, true); } int amountMana = xValue * variableManaCost.getXInstancesCount(); diff --git a/Mage/src/main/java/mage/abilities/ActivatedAbility.java b/Mage/src/main/java/mage/abilities/ActivatedAbility.java index 4cf270557ac..02205c61626 100644 --- a/Mage/src/main/java/mage/abilities/ActivatedAbility.java +++ b/Mage/src/main/java/mage/abilities/ActivatedAbility.java @@ -99,6 +99,11 @@ public interface ActivatedAbility extends Ability { int getMaxActivationsPerTurn(Game game); + /** + * how many more time can this be activated this turn? + */ + int getMaxMoreActivationsThisTurn(Game game); + ActivatedAbility setTiming(TimingRule timing); ActivatedAbility setCondition(Condition condition); diff --git a/Mage/src/main/java/mage/abilities/ActivatedAbilityImpl.java b/Mage/src/main/java/mage/abilities/ActivatedAbilityImpl.java index 60c803102ee..0290cb0b6e1 100644 --- a/Mage/src/main/java/mage/abilities/ActivatedAbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/ActivatedAbilityImpl.java @@ -215,6 +215,23 @@ public abstract class ActivatedAbilityImpl extends AbilityImpl implements Activa || activationInfo.activationCounter < getMaxActivationsPerTurn(game); } + public int getMaxMoreActivationsThisTurn(Game game) { + if (getMaxActivationsPerTurn(game) == Integer.MAX_VALUE && maxActivationsPerGame == Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } + ActivationInfo activationInfo = getActivationInfo(game); + if (activationInfo == null) { + return Math.min(maxActivationsPerGame, getMaxActivationsPerTurn(game)); + } + if (activationInfo.totalActivations >= maxActivationsPerGame) { + return 0; + } + if (activationInfo.turnNum != game.getTurnNum()) { + return getMaxActivationsPerTurn(game); + } + return Math.max(0, getMaxActivationsPerTurn(game) - activationInfo.activationCounter); + } + @Override public boolean activate(Game game, Set allowedIdentifiers, boolean noMana) { if (!hasMoreActivationsThisTurn(game) || !super.activate(game, allowedIdentifiers, noMana)) { diff --git a/Mage/src/main/java/mage/abilities/Modes.java b/Mage/src/main/java/mage/abilities/Modes.java index fa84ea9e0db..c90bf1c8bbc 100644 --- a/Mage/src/main/java/mage/abilities/Modes.java +++ b/Mage/src/main/java/mage/abilities/Modes.java @@ -617,18 +617,16 @@ public class Modes extends LinkedHashMap implements Copyable sb.append("
"); for (Mode mode : this.values()) { - if (mode.getCost() != null) { + if (mode.getCost() != null && mode.getFlavorWord() == null) { // for Spree sb.append("+ "); - sb.append(mode.getCost().getText()); - sb.append(" — "); } else if (mode.getPawPrintValue() > 0) { for (int i = 0; i < mode.getPawPrintValue(); ++i) { sb.append("{P}"); } sb.append(" — "); } else { - sb.append("&bull "); + sb.append("&bull "); } sb.append(mode.getEffects().getTextStartingUpperCase(mode)); sb.append("
"); diff --git a/Mage/src/main/java/mage/abilities/common/CounterRemovedFromSourceWhileExiledTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/CounterRemovedFromSourceWhileExiledTriggeredAbility.java index 4d4812e77d7..e8dc04e60f8 100644 --- a/Mage/src/main/java/mage/abilities/common/CounterRemovedFromSourceWhileExiledTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/CounterRemovedFromSourceWhileExiledTriggeredAbility.java @@ -29,7 +29,7 @@ public class CounterRemovedFromSourceWhileExiledTriggeredAbility extends Trigger this.onlyController = onlyController; setTriggerPhrase("Whenever " + ( onlyController ? ("you remove a " + counterType.getName() + " counter") : ("a " + counterType.getName() + " counter is removed") - ) + " from {this} while it's exiled, "); + ) + " from this card while it's exiled, "); } private CounterRemovedFromSourceWhileExiledTriggeredAbility(final CounterRemovedFromSourceWhileExiledTriggeredAbility ability) { diff --git a/Mage/src/main/java/mage/abilities/common/DealsDamageToACreatureTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DealsDamageToACreatureTriggeredAbility.java index e938a7fa0ec..d5fd45bf272 100644 --- a/Mage/src/main/java/mage/abilities/common/DealsDamageToACreatureTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DealsDamageToACreatureTriggeredAbility.java @@ -59,7 +59,7 @@ public class DealsDamageToACreatureTriggeredAbility extends TriggeredAbilityImpl if (!filter.match(creature, getControllerId(), this, game)) { return false; } - + this.getEffects().setValue("damagedCreature", game.getPermanent(event.getTargetId())); if (setTargetPointer) { this.getEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game)); this.getEffects().setValue("damage", event.getAmount()); diff --git a/Mage/src/main/java/mage/abilities/common/DealsDamageToAnyTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DealsDamageToAnyTriggeredAbility.java index 9f2ff4b1efd..25011ae35e5 100644 --- a/Mage/src/main/java/mage/abilities/common/DealsDamageToAnyTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DealsDamageToAnyTriggeredAbility.java @@ -15,6 +15,8 @@ import mage.target.targetpointer.FixedTarget; import mage.util.CardUtil; /** + * Whenever [[filtered permanent]] deals (combat)? damage, [[effect]] + * * @author xenohedron */ public class DealsDamageToAnyTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { diff --git a/Mage/src/main/java/mage/abilities/common/DealsDamageToOpponentTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DealsDamageToOpponentTriggeredAbility.java index 5ad6f7c461e..93ce1917be3 100644 --- a/Mage/src/main/java/mage/abilities/common/DealsDamageToOpponentTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DealsDamageToOpponentTriggeredAbility.java @@ -55,22 +55,16 @@ public class DealsDamageToOpponentTriggeredAbility extends TriggeredAbilityImpl @Override public boolean checkTrigger(GameEvent event, Game game) { - if (event.getSourceId().equals(this.sourceId) - && game.getOpponents(this.getControllerId()).contains(event.getTargetId())) { - if (onlyCombat && event instanceof DamagedPlayerEvent) { - DamagedPlayerEvent damageEvent = (DamagedPlayerEvent) event; - if (!damageEvent.isCombatDamage()) { - return false; - } - } - if (setTargetPointer) { - for (Effect effect : getEffects()) { - effect.setTargetPointer(new FixedTarget(event.getTargetId(), game)); - effect.setValue("damage", event.getAmount()); - } - } - return true; + if (!event.getSourceId().equals(this.getSourceId()) + || !game.getOpponents(this.getControllerId()).contains(event.getTargetId()) + || onlyCombat + && !((DamagedPlayerEvent) event).isCombatDamage()) { + return false; } - return false; + this.getEffects().setValue("damage", event.getAmount()); + if (setTargetPointer) { + this.getEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game)); + } + return true; } } diff --git a/Mage/src/main/java/mage/abilities/common/DealtDamageAttachedAndDiedTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DealtDamageAttachedAndDiedTriggeredAbility.java index 04a36d16338..c7142e68b45 100644 --- a/Mage/src/main/java/mage/abilities/common/DealtDamageAttachedAndDiedTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DealtDamageAttachedAndDiedTriggeredAbility.java @@ -7,7 +7,6 @@ import mage.abilities.effects.Effect; import mage.constants.AttachmentType; import mage.constants.SetTargetPointer; import mage.constants.Zone; -import mage.filter.StaticFilters; import mage.filter.common.FilterCreaturePermanent; import mage.game.Game; import mage.game.events.GameEvent; @@ -21,10 +20,6 @@ public class DealtDamageAttachedAndDiedTriggeredAbility extends TriggeredAbility private final FilterCreaturePermanent filter; private final SetTargetPointer setTargetPointer; - public DealtDamageAttachedAndDiedTriggeredAbility(Effect effect, boolean optional) { - this(effect, optional, StaticFilters.FILTER_PERMANENT_CREATURE, SetTargetPointer.PERMANENT, AttachmentType.EQUIPMENT); - } - public DealtDamageAttachedAndDiedTriggeredAbility(Effect effect, boolean optional, FilterCreaturePermanent filter, SetTargetPointer setTargetPointer, AttachmentType attachmentType) { super(Zone.ALL, effect, optional); @@ -72,8 +67,17 @@ public class DealtDamageAttachedAndDiedTriggeredAbility extends TriggeredAbility })) { return false; } - if (this.setTargetPointer == SetTargetPointer.PERMANENT) { - getEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game)); + switch (setTargetPointer) { + case PERMANENT: + getEffects().setTargetPointer(new FixedTarget(creatureMOR)); + break; + case CARD: + getEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game)); + break; + case NONE: + break; + default: + throw new IllegalArgumentException("Unsupported setTargetPointer value in DealtDamageAttachedAndDiedTriggeredAbility"); } return true; } diff --git a/Mage/src/main/java/mage/abilities/common/DiesThisOrAnotherTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DiesThisOrAnotherTriggeredAbility.java index e55160ee720..84eb7907f16 100644 --- a/Mage/src/main/java/mage/abilities/common/DiesThisOrAnotherTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DiesThisOrAnotherTriggeredAbility.java @@ -29,7 +29,7 @@ public class DiesThisOrAnotherTriggeredAbility extends TriggeredAbilityImpl { if (filterMessage.startsWith("a ")) { filterMessage = filterMessage.substring(2); } - setTriggerPhrase("Whenever {this} or another " + filterMessage + " dies, "); + setTriggerPhrase("Whenever {this} or " + (filterMessage.startsWith("another") ? "" : "another ") + filterMessage + " dies, "); setLeavesTheBattlefieldTrigger(true); } @@ -61,8 +61,12 @@ public class DiesThisOrAnotherTriggeredAbility extends TriggeredAbilityImpl { return false; } // TODO: remove applyFilterOnSource workaround for Basri's Lieutenant - return ((!applyFilterOnSource && zEvent.getTarget().getId().equals(this.getSourceId())) - || filter.match(zEvent.getTarget(), getControllerId(), this, game)); + if ((applyFilterOnSource || !zEvent.getTarget().getId().equals(this.getSourceId())) + && !filter.match(zEvent.getTarget(), getControllerId(), this, game)) { + return false; + } + this.getEffects().setValue("creatureDied", zEvent.getTarget()); + return true; } @Override diff --git a/Mage/src/main/java/mage/abilities/common/DrawCardOpponentTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DrawCardOpponentTriggeredAbility.java index 42f5f4dae2d..f470a7577aa 100644 --- a/Mage/src/main/java/mage/abilities/common/DrawCardOpponentTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DrawCardOpponentTriggeredAbility.java @@ -44,6 +44,7 @@ public class DrawCardOpponentTriggeredAbility extends TriggeredAbilityImpl { if (!game.getPlayer(this.getControllerId()).hasOpponent(event.getPlayerId(), game)) { return false; } + this.getEffects().setValue("playerDrew", event.getPlayerId()); if (setTargetPointer) { this.getEffects().setTargetPointer(new FixedTarget(event.getPlayerId())); } diff --git a/Mage/src/main/java/mage/abilities/common/SagaAbility.java b/Mage/src/main/java/mage/abilities/common/SagaAbility.java index 5bc4a46236d..803556ae727 100644 --- a/Mage/src/main/java/mage/abilities/common/SagaAbility.java +++ b/Mage/src/main/java/mage/abilities/common/SagaAbility.java @@ -6,11 +6,13 @@ import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; import mage.abilities.effects.Effects; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.ReadAheadAbility; import mage.cards.Card; -import mage.constants.Outcome; -import mage.constants.SagaChapter; -import mage.constants.Zone; +import mage.constants.*; import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; @@ -47,9 +49,12 @@ public class SagaAbility extends SimpleStaticAbility { this.readAhead = readAhead; this.setRuleVisible(true); this.setRuleAtTheTop(true); - Ability ability = new EntersBattlefieldAbility(new SagaLoreCountersEffect(readAhead, maxChapter)); + Ability ability = new EntersBattlefieldAbility(new SagaLoreCountersEffect(maxChapter)); ability.setRuleVisible(false); card.addAbility(ability); + if (readAhead) { + card.addAbility(ReadAheadAbility.getInstance()); + } } protected SagaAbility(final SagaAbility ability) { @@ -115,7 +120,7 @@ public class SagaAbility extends SimpleStaticAbility { public void addChapterEffect(Card card, SagaChapter fromChapter, SagaChapter toChapter, boolean optional, Consumer applier) { for (int i = fromChapter.getNumber(); i <= toChapter.getNumber(); i++) { ChapterTriggeredAbility ability = new ChapterTriggeredAbility( - SagaChapter.getChapter(i), toChapter, optional, readAhead + SagaChapter.getChapter(i), toChapter, optional ); applier.accept(ability); if (i > fromChapter.getNumber()) { @@ -132,7 +137,7 @@ public class SagaAbility extends SimpleStaticAbility { @Override public String getRule() { return (readAhead - ? "Read ahead (Choose a chapter and start with that many lore counters. " + + ? "(Choose a chapter and start with that many lore counters. " + "Add one after your draw step. Skipped chapters don't trigger." : "(As this Saga enters and after your draw step, add a lore counter.") + (showSacText ? " Sacrifice after " + maxChapter.toString() + '.' : "") + ") "; @@ -158,22 +163,23 @@ public class SagaAbility extends SimpleStaticAbility { return ability instanceof ChapterTriggeredAbility && ((ChapterTriggeredAbility) ability).getChapterFrom().getNumber() == maxChapter; } + + public static Ability makeGainReadAheadAbility() { + return new GainReadAheadAbility(); + } } class SagaLoreCountersEffect extends OneShotEffect { - private final boolean readAhead; private final SagaChapter maxChapter; - SagaLoreCountersEffect(boolean readAhead, SagaChapter maxChapter) { + SagaLoreCountersEffect(SagaChapter maxChapter) { super(Outcome.Benefit); - this.readAhead = readAhead; this.maxChapter = maxChapter; } private SagaLoreCountersEffect(final SagaLoreCountersEffect effect) { super(effect); - this.readAhead = effect.readAhead; this.maxChapter = effect.maxChapter; } @@ -188,7 +194,8 @@ class SagaLoreCountersEffect extends OneShotEffect { if (permanent == null) { return false; } - if (!readAhead) { + if (!permanent.hasAbility(ReadAheadAbility.getInstance(), game) + && !GainReadAheadAbility.checkForAbility(game, source)) { return permanent.addCounters(CounterType.LORE.createInstance(), source, game); } Player player = game.getPlayer(source.getControllerId()); @@ -197,7 +204,7 @@ class SagaLoreCountersEffect extends OneShotEffect { } int counters = player.getAmount( 1, maxChapter.getNumber(), - "Choose the number of lore counters to enter with", game + "Choose the number of lore counters to enter with", source, game ); return permanent.addCounters(CounterType.LORE.createInstance(counters), source, game); } @@ -206,20 +213,17 @@ class SagaLoreCountersEffect extends OneShotEffect { class ChapterTriggeredAbility extends TriggeredAbilityImpl { private final SagaChapter chapterFrom, chapterTo; - private final boolean readAhead; - ChapterTriggeredAbility(SagaChapter chapterFrom, SagaChapter chapterTo, boolean optional, boolean readAhead) { + ChapterTriggeredAbility(SagaChapter chapterFrom, SagaChapter chapterTo, boolean optional) { super(Zone.ALL, null, optional); this.chapterFrom = chapterFrom; this.chapterTo = chapterTo; - this.readAhead = readAhead; } private ChapterTriggeredAbility(final ChapterTriggeredAbility ability) { super(ability); this.chapterFrom = ability.chapterFrom; this.chapterTo = ability.chapterTo; - this.readAhead = ability.readAhead; } @Override @@ -238,7 +242,9 @@ class ChapterTriggeredAbility extends TriggeredAbilityImpl { return false; } int loreCounters = permanent.getCounters(game).getCount(CounterType.LORE); - if (readAhead && permanent.getTurnsOnBattlefield() == 0) { + if (permanent.getTurnsOnBattlefield() == 0 + && (permanent.hasAbility(ReadAheadAbility.getInstance(), game) + || GainReadAheadAbility.checkForAbility(game, this))) { return chapterFrom.getNumber() == loreCounters; } return loreCounters - event.getAmount() < chapterFrom.getNumber() @@ -275,3 +281,35 @@ class ChapterTriggeredAbility extends TriggeredAbilityImpl { + " - " + CardUtil.getTextWithFirstCharUpperCase(super.getRule()); } } + +class GainReadAheadAbility extends SimpleStaticAbility { + + private static final FilterPermanent filter = new FilterPermanent(SubType.SAGA, "Sagas"); + + GainReadAheadAbility() { + super(new GainAbilityControlledEffect( + ReadAheadAbility.getInstance(), Duration.WhileOnBattlefield, filter + ).setText("Sagas you control have read ahead. (As a Saga enters, choose a chapter " + + "and start with that many lore counters. Skipped chapters don't trigger.)")); + } + + private GainReadAheadAbility(final GainReadAheadAbility ability) { + super(ability); + } + + @Override + public GainReadAheadAbility copy() { + return new GainReadAheadAbility(this); + } + + static boolean checkForAbility(Game game, Ability source) { + return game + .getBattlefield() + .getActivePermanents( + StaticFilters.FILTER_CONTROLLED_PERMANENT, + source.getControllerId(), source, game + ) + .stream() + .anyMatch(p -> p.getAbilities(game).containsClass(GainReadAheadAbility.class)); + } +} diff --git a/Mage/src/main/java/mage/abilities/common/SpellCastOpponentNoManaSpentTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/SpellCastOpponentNoManaSpentTriggeredAbility.java deleted file mode 100644 index 46bf4418db4..00000000000 --- a/Mage/src/main/java/mage/abilities/common/SpellCastOpponentNoManaSpentTriggeredAbility.java +++ /dev/null @@ -1,48 +0,0 @@ -package mage.abilities.common; - -import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.effects.Effect; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.stack.Spell; -import mage.target.targetpointer.FixedTarget; - -/** - * @author Susucr - */ -public class SpellCastOpponentNoManaSpentTriggeredAbility extends TriggeredAbilityImpl { - - public SpellCastOpponentNoManaSpentTriggeredAbility(Effect effect) { - super(Zone.BATTLEFIELD, effect, false); - this.setTriggerPhrase("Whenever an opponent casts a spell, if no mana was spent to cast it, "); - } - - protected SpellCastOpponentNoManaSpentTriggeredAbility(final SpellCastOpponentNoManaSpentTriggeredAbility ability) { - super(ability); - } - - @Override - public SpellCastOpponentNoManaSpentTriggeredAbility copy() { - return new SpellCastOpponentNoManaSpentTriggeredAbility(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 (game.getPlayer(this.getControllerId()).hasOpponent(event.getPlayerId(), game)) { - Spell spell = game.getStack().getSpell(event.getTargetId()); - if (spell != null && spell.getStackAbility().getManaCostsToPay().getUsedManaToPay().count() == 0) { - for (Effect effect : this.getEffects()) { - effect.setTargetPointer(new FixedTarget(event.getTargetId())); - } - return true; - } - } - return false; - } -} \ No newline at end of file diff --git a/Mage/src/main/java/mage/abilities/common/WonCoinFlipControllerTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/WonCoinFlipControllerTriggeredAbility.java new file mode 100644 index 00000000000..2510d4f8c4e --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/WonCoinFlipControllerTriggeredAbility.java @@ -0,0 +1,46 @@ +package mage.abilities.common; + +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.Effect; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.CoinFlippedEvent; +import mage.game.events.GameEvent; + +/** + * @author TheElk801 + */ +public class WonCoinFlipControllerTriggeredAbility extends TriggeredAbilityImpl { + + public WonCoinFlipControllerTriggeredAbility(Effect effect) { + this(effect, false); + } + + public WonCoinFlipControllerTriggeredAbility(Effect effect, boolean optional) { + this(Zone.BATTLEFIELD, effect, optional); + } + + public WonCoinFlipControllerTriggeredAbility(Zone zone, Effect effect, boolean optional) { + super(zone, effect, optional); + } + + private WonCoinFlipControllerTriggeredAbility(final WonCoinFlipControllerTriggeredAbility ability) { + super(ability); + } + + @Override + public WonCoinFlipControllerTriggeredAbility copy() { + return new WonCoinFlipControllerTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.COIN_FLIPPED; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + CoinFlippedEvent flipEvent = (CoinFlippedEvent) event; + return isControlledBy(event.getPlayerId()) && flipEvent.isWinnable() && flipEvent.wasWon(); + } +} diff --git a/Mage/src/main/java/mage/abilities/condition/common/AttackedThisTurnSourceCondition.java b/Mage/src/main/java/mage/abilities/condition/common/AttackedThisTurnSourceCondition.java index 54d20484f46..a2feceff9e7 100644 --- a/Mage/src/main/java/mage/abilities/condition/common/AttackedThisTurnSourceCondition.java +++ b/Mage/src/main/java/mage/abilities/condition/common/AttackedThisTurnSourceCondition.java @@ -1,4 +1,3 @@ - package mage.abilities.condition.common; import mage.MageObjectReference; @@ -9,7 +8,6 @@ import mage.game.permanent.Permanent; import mage.watchers.common.AttackedThisTurnWatcher; /** - * * @author LevelX2 */ public enum AttackedThisTurnSourceCondition implements Condition { @@ -21,4 +19,9 @@ public enum AttackedThisTurnSourceCondition implements Condition { AttackedThisTurnWatcher watcher = game.getState().getWatcher(AttackedThisTurnWatcher.class); return sourcePermanent != null && watcher.getAttackedThisTurnCreatures().contains(new MageObjectReference(sourcePermanent, game)); } + + @Override + public String toString() { + return "{this} attacked this turn"; + } } diff --git a/Mage/src/main/java/mage/abilities/condition/common/KickedCondition.java b/Mage/src/main/java/mage/abilities/condition/common/KickedCondition.java index e0b40c02e34..454f42d3071 100644 --- a/Mage/src/main/java/mage/abilities/condition/common/KickedCondition.java +++ b/Mage/src/main/java/mage/abilities/condition/common/KickedCondition.java @@ -29,6 +29,6 @@ public enum KickedCondition implements Condition { @Override public String toString() { - return "{this} was kicked" + (text.isEmpty() ? "" : " " + text); + return "it was kicked" + (text.isEmpty() ? "" : " " + text); } } diff --git a/Mage/src/main/java/mage/abilities/condition/common/RevoltCondition.java b/Mage/src/main/java/mage/abilities/condition/common/RevoltCondition.java index 530f657ca38..35a217f742c 100644 --- a/Mage/src/main/java/mage/abilities/condition/common/RevoltCondition.java +++ b/Mage/src/main/java/mage/abilities/condition/common/RevoltCondition.java @@ -31,6 +31,6 @@ public enum RevoltCondition implements Condition { @Override public String toString() { - return "a permanent you controlled left the battlefield this turn"; + return "a permanent left the battlefield under your control this turn"; } } diff --git a/Mage/src/main/java/mage/abilities/costs/VariableCostImpl.java b/Mage/src/main/java/mage/abilities/costs/VariableCostImpl.java index 8a1ccfde03c..d08ff68bfc7 100644 --- a/Mage/src/main/java/mage/abilities/costs/VariableCostImpl.java +++ b/Mage/src/main/java/mage/abilities/costs/VariableCostImpl.java @@ -158,8 +158,8 @@ public abstract class VariableCostImpl implements Cost, VariableCost { if (controller != null && (source instanceof ManaAbility || stackObject != null)) { - xValue = controller.announceXCost(getMinValue(source, game), getMaxValue(source, game), - "Announce the number of " + actionText, game, source, this); + xValue = controller.announceX(getMinValue(source, game), getMaxValue(source, game), + "Announce the value for {X} (" + actionText + ")", game, source, false); } return xValue; } diff --git a/Mage/src/main/java/mage/abilities/costs/common/CollectEvidenceCost.java b/Mage/src/main/java/mage/abilities/costs/common/CollectEvidenceCost.java index f8ceb580131..04cc1ee9195 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/CollectEvidenceCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/CollectEvidenceCost.java @@ -89,7 +89,7 @@ public class CollectEvidenceCost extends CostImpl { ); } }.withNotTarget(true); - player.choose(Outcome.Exile, target, source, game); + target.choose(Outcome.Exile, player.getId(), source.getSourceId(), source, game); Cards cards = new CardsImpl(target.getTargets()); paid = cards .getCards(game) diff --git a/Mage/src/main/java/mage/abilities/costs/common/DiscardSourceCost.java b/Mage/src/main/java/mage/abilities/costs/common/DiscardSourceCost.java index 96188f942d6..2fa756b6f57 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/DiscardSourceCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/DiscardSourceCost.java @@ -12,22 +12,17 @@ import mage.players.Player; import java.util.UUID; /** - * * @author BetaSteward_at_googlemail.com */ public class DiscardSourceCost extends CostImpl { - private boolean nameCard = true; - - public DiscardSourceCost() {} - - public DiscardSourceCost(boolean nameCard){ - this.nameCard = nameCard; + public DiscardSourceCost() { + super(); + setText("discard this card"); } public DiscardSourceCost(DiscardSourceCost cost) { super(cost); - nameCard = cost.nameCard; } @Override @@ -41,21 +36,10 @@ public class DiscardSourceCost extends CostImpl { if (player != null) { Card card = player.getHand().get(source.getSourceId(), game); paid = player.discard(card, true, source, game); - } return paid; } - @Override - public String getText() { - if(nameCard) { - return "Discard {this}"; - } - else{ - return "Discard this card"; - } - } - @Override public DiscardSourceCost copy() { return new DiscardSourceCost(this); diff --git a/Mage/src/main/java/mage/abilities/costs/common/DiscardTargetCost.java b/Mage/src/main/java/mage/abilities/costs/common/DiscardTargetCost.java index 70d268f8dd5..83b12475790 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/DiscardTargetCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/DiscardTargetCost.java @@ -47,7 +47,7 @@ public class DiscardTargetCost extends CostImpl { if (player == null) { return false; } - int amount = this.getTargets().get(0).getNumberOfTargets(); + int amount = this.getTargets().get(0).getMinNumberOfTargets(); if (randomDiscard) { this.cards.addAll(player.discard(amount, true, true, source, game).getCards(game)); } else if (this.getTargets().choose(Outcome.Discard, controllerId, source.getSourceId(), source, game)) { diff --git a/Mage/src/main/java/mage/abilities/costs/common/ExileFromGraveCost.java b/Mage/src/main/java/mage/abilities/costs/common/ExileFromGraveCost.java index e3b234cb4e2..552ab89af2d 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/ExileFromGraveCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/ExileFromGraveCost.java @@ -33,9 +33,9 @@ public class ExileFromGraveCost extends CostImpl { this.addTarget(target); if (target.getMaxNumberOfTargets() > 1) { this.text = "exile " - + (target.getNumberOfTargets() == 1 + + (target.getMinNumberOfTargets() == 1 && target.getMaxNumberOfTargets() == Integer.MAX_VALUE ? "one or more" - : ((target.getNumberOfTargets() < target.getMaxNumberOfTargets() ? "up to " : "")) + : ((target.getMinNumberOfTargets() < target.getMaxNumberOfTargets() ? "up to " : "")) + CardUtil.numberToText(target.getMaxNumberOfTargets())) + ' ' + target.getTargetName(); } else { diff --git a/Mage/src/main/java/mage/abilities/costs/common/ExileSourceWithTimeCountersCost.java b/Mage/src/main/java/mage/abilities/costs/common/ExileSourceWithTimeCountersCost.java deleted file mode 100644 index 4ce88003b7a..00000000000 --- a/Mage/src/main/java/mage/abilities/costs/common/ExileSourceWithTimeCountersCost.java +++ /dev/null @@ -1,88 +0,0 @@ -package mage.abilities.costs.common; - -import java.util.Locale; -import java.util.UUID; - -import mage.abilities.Ability; -import mage.abilities.costs.Cost; -import mage.abilities.costs.CostImpl; -import mage.abilities.effects.common.continuous.GainSuspendEffect; -import mage.abilities.keyword.SuspendAbility; -import mage.cards.Card; -import mage.constants.Zone; -import mage.counters.CounterType; -import mage.game.Game; -import mage.MageObjectReference; -import mage.players.Player; - - -/** - * @author padfoot - */ -public class ExileSourceWithTimeCountersCost extends CostImpl { - - private final int counters; - private final boolean checksSuspend; - private final boolean givesSuspend; - private final Zone fromZone; - - public ExileSourceWithTimeCountersCost(int counters) { - this (counters, true, false, null); - } - - public ExileSourceWithTimeCountersCost(int counters, boolean givesSuspend, boolean checksSuspend, Zone fromZone) { - this.counters = counters; - this.givesSuspend = givesSuspend; - this.checksSuspend = checksSuspend; - this.fromZone = fromZone; - this.text = "exile {this} " + - ((fromZone != null) ? " from your " + fromZone.toString().toLowerCase(Locale.ENGLISH) : "") + - " and put " + counters + " time counters on it" + - (givesSuspend ? ". It gains suspend" : "") + - (checksSuspend ? ". If it doesn't have suspend, it gains suspend" : ""); - } - - private ExileSourceWithTimeCountersCost(final ExileSourceWithTimeCountersCost cost) { - super(cost); - this.counters = cost.counters; - this.givesSuspend = cost.givesSuspend; - this.checksSuspend = cost.checksSuspend; - this.fromZone = cost.fromZone; - } - - @Override - public boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana, Cost costToPay) { - Player controller = game.getPlayer(controllerId); - if (controller == null) { - return paid; - } - Card card = game.getCard(source.getSourceId()); - boolean hasSuspend = card.getAbilities(game).containsClass(SuspendAbility.class); - if (card != null && (fromZone == null || fromZone == game.getState().getZone(source.getSourceId()))) { - UUID exileId = SuspendAbility.getSuspendExileId(controller.getId(), game); - if (controller.moveCardsToExile(card, source, game, true, exileId, "Suspended cards of " + controller.getName())) { - card.addCounters(CounterType.TIME.createInstance(counters), controller.getId(), source, game); - game.informPlayers(controller.getLogName() + " exiles " + card.getLogName() + ((fromZone != null) ? " from their " + fromZone.toString().toLowerCase(Locale.ENGLISH) : "") + " with " + counters + " time counters on it."); - if (givesSuspend || (checksSuspend && !hasSuspend)) { - game.addEffect(new GainSuspendEffect(new MageObjectReference(card, game)), source); - } - } - // 117.11. The actions performed when paying a cost may be modified by effects. - // Even if they are, meaning the actions that are performed don't match the actions - // that are called for, the cost has still been paid. - // so return state here is not important because the user indended to exile the target anyway - paid = true; - } - return paid; - } - - @Override - public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return (game.getCard(source.getSourceId()) != null && (fromZone == null || fromZone == game.getState().getZone(source.getSourceId()))); - } - - @Override - public ExileSourceWithTimeCountersCost copy() { - return new ExileSourceWithTimeCountersCost(this); - } -} diff --git a/Mage/src/main/java/mage/abilities/costs/common/PutCountersSourceCost.java b/Mage/src/main/java/mage/abilities/costs/common/PutCountersSourceCost.java index e89fc782fab..daf16a82915 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/PutCountersSourceCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/PutCountersSourceCost.java @@ -10,7 +10,6 @@ import mage.game.permanent.Permanent; import java.util.UUID; /** - * * @author jeffwadsworth */ public class PutCountersSourceCost extends CostImpl { @@ -29,6 +28,7 @@ public class PutCountersSourceCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { + // TODO: implement permanent.canAddCounters with replacement events check, see tests with Devoted Druid return true; } 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 15f52b824ad..03c1607a1fb 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/RemoveCounterCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/RemoveCounterCost.java @@ -136,7 +136,8 @@ public class RemoveCounterCost extends CostImpl { int numberOfCountersSelected = 1; if (countersLeft > 1 && countersOnPermanent > 1) { numberOfCountersSelected = controller.getAmount(1, Math.min(countersLeft, countersOnPermanent), - "Choose how many counters (" + counterName + ") to remove from " + targetObject.getLogName() + " as payment", game); + "Choose how many counters (" + counterName + ") to remove from " + targetObject.getLogName() + " as payment", + source, game); } targetObject.removeCounters(counterName, numberOfCountersSelected, source, game); countersRemoved += numberOfCountersSelected; diff --git a/Mage/src/main/java/mage/abilities/costs/common/ReturnToHandChosenControlledPermanentCost.java b/Mage/src/main/java/mage/abilities/costs/common/ReturnToHandChosenControlledPermanentCost.java index 5d2144e5244..640f9c7b0d3 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/ReturnToHandChosenControlledPermanentCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/ReturnToHandChosenControlledPermanentCost.java @@ -25,7 +25,7 @@ public class ReturnToHandChosenControlledPermanentCost extends CostImpl { public ReturnToHandChosenControlledPermanentCost(TargetControlledPermanent target) { target.withNotTarget(true); this.addTarget(target); - if (target.getMaxNumberOfTargets() > 1 && target.getMaxNumberOfTargets() == target.getNumberOfTargets()) { + if (target.getMaxNumberOfTargets() > 1 && target.getMaxNumberOfTargets() == target.getMinNumberOfTargets()) { this.text = "return " + CardUtil.numberToText(target.getMaxNumberOfTargets()) + ' ' + target.getTargetName() + (target.getTargetName().endsWith(" you control") ? "" : " you control") diff --git a/Mage/src/main/java/mage/abilities/costs/common/RevealTargetFromHandCost.java b/Mage/src/main/java/mage/abilities/costs/common/RevealTargetFromHandCost.java index bc0895c69c3..6ce286b3e5e 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/RevealTargetFromHandCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/RevealTargetFromHandCost.java @@ -29,7 +29,7 @@ public class RevealTargetFromHandCost extends CostImpl { public RevealTargetFromHandCost(TargetCardInHand target) { this.addTarget(target); - this.allowNoReveal = target.getNumberOfTargets() == 0; + this.allowNoReveal = target.getMinNumberOfTargets() == 0; this.text = "reveal " + target.getDescription(); this.revealedCards = new ArrayList<>(); } @@ -62,7 +62,7 @@ public class RevealTargetFromHandCost extends CostImpl { MageObject baseObject = game.getBaseObject(source.getSourceId()); player.revealCards(baseObject == null ? "card cost" : baseObject.getIdName(), cards, game); } - if (this.getTargets().get(0).getNumberOfTargets() <= numberCardsRevealed) { + if (this.getTargets().get(0).getMinNumberOfTargets() <= numberCardsRevealed) { paid = true; // e.g. for optional additional costs. example: Dragonlord's Prerogative also true if 0 cards shown return paid; } diff --git a/Mage/src/main/java/mage/abilities/costs/common/SacrificeTargetCost.java b/Mage/src/main/java/mage/abilities/costs/common/SacrificeTargetCost.java index 4fa11cbffc4..e64d4a83a17 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/SacrificeTargetCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/SacrificeTargetCost.java @@ -67,7 +67,7 @@ public class SacrificeTargetCost extends CostImpl implements SacrificeCost { addSacrificeTarget(game, permanent); paid |= permanent.sacrifice(source, game); } - if (!paid && this.getTargets().get(0).getNumberOfTargets() == 0) { + if (!paid && this.getTargets().get(0).getMinNumberOfTargets() == 0) { paid = true; // e.g. for Devouring Rage } } @@ -88,7 +88,7 @@ public class SacrificeTargetCost extends CostImpl implements SacrificeCost { return false; } int validTargets = 0; - int neededTargets = this.getTargets().get(0).getNumberOfTargets(); + int neededTargets = this.getTargets().get(0).getMinNumberOfTargets(); for (Permanent permanent : game.getBattlefield().getActivePermanents(((TargetPermanent) this.getTargets().get(0)).getFilter(), controllerId, source, game)) { if (controller.canPaySacrificeCost(permanent, source, controllerId, game)) { validTargets++; @@ -114,11 +114,11 @@ public class SacrificeTargetCost extends CostImpl implements SacrificeCost { if (target.getMinNumberOfTargets() != target.getMaxNumberOfTargets()) { return target.getTargetName(); } - if (target.getNumberOfTargets() == 1 + if (target.getMinNumberOfTargets() == 1 || target.getTargetName().startsWith("a ") || target.getTargetName().startsWith("an ")) { return CardUtil.addArticle(target.getTargetName()); } - return CardUtil.numberToText(target.getNumberOfTargets()) + ' ' + target.getTargetName(); + return CardUtil.numberToText(target.getMinNumberOfTargets()) + ' ' + target.getTargetName(); } } diff --git a/Mage/src/main/java/mage/abilities/costs/common/TapTargetCost.java b/Mage/src/main/java/mage/abilities/costs/common/TapTargetCost.java index 4a5e1b3d4b4..0d8a5b77059 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/TapTargetCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/TapTargetCost.java @@ -24,7 +24,7 @@ public class TapTargetCost extends CostImpl { this.target = target; this.target.withNotTarget(true); // costs are never targeted this.target.setRequired(false); // can be cancel by user - this.text = "tap " + (target.getNumberOfTargets() > 1 + this.text = "tap " + (target.getMinNumberOfTargets() > 1 ? CardUtil.numberToText(target.getMaxNumberOfTargets()) + ' ' + target.getTargetName() : CardUtil.addArticle(target.getTargetName())); } @@ -47,7 +47,7 @@ public class TapTargetCost extends CostImpl { permanents.add(permanent); } } - if (target.getNumberOfTargets() == 0) { + if (target.getMinNumberOfTargets() == 0) { paid = true; // e.g. Aryel with X = 0 } source.getEffects().setValue("tappedPermanents", permanents); diff --git a/Mage/src/main/java/mage/abilities/costs/common/UntapSourceCost.java b/Mage/src/main/java/mage/abilities/costs/common/UntapSourceCost.java index 2e379faac2a..5d0d8af69ab 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/UntapSourceCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/UntapSourceCost.java @@ -1,7 +1,6 @@ package mage.abilities.costs.common; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.costs.Cost; import mage.abilities.costs.CostImpl; @@ -10,6 +9,8 @@ import mage.counters.CounterType; import mage.game.Game; import mage.game.permanent.Permanent; +import java.util.UUID; + /** * * @author Plopman @@ -43,7 +44,7 @@ public class UntapSourceCost extends CostImpl { public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { Permanent permanent = game.getPermanent(source.getSourceId()); if (permanent != null) { - return permanent.isTapped() && (permanent.canTap(game) || game.getContinuousEffects().asThough(source.getSourceId(), AsThoughEffectType.ACTIVATE_HASTE, ability, controllerId, game).isEmpty()); + return permanent.isTapped() && (permanent.canTap(game) || !game.getContinuousEffects().asThough(source.getSourceId(), AsThoughEffectType.ACTIVATE_HASTE, ability, controllerId, game).isEmpty()); } return false; } diff --git a/Mage/src/main/java/mage/abilities/costs/costadjusters/ExileCardsFromHandAdjuster.java b/Mage/src/main/java/mage/abilities/costs/costadjusters/ExileCardsFromHandAdjuster.java index 9d9d287eae2..f438ed65c45 100644 --- a/Mage/src/main/java/mage/abilities/costs/costadjusters/ExileCardsFromHandAdjuster.java +++ b/Mage/src/main/java/mage/abilities/costs/costadjusters/ExileCardsFromHandAdjuster.java @@ -40,7 +40,7 @@ public class ExileCardsFromHandAdjuster implements CostAdjuster { // real - need to choose // TODO: need early target cost instead dialog here int toExile = cardCount == 0 ? 0 : player.getAmount( - 0, cardCount, "Choose how many " + filter.getMessage() + " to exile", game + 0, cardCount, "Choose how many " + filter.getMessage() + " to exile", ability, game ); reduceCount = 2 * toExile; if (toExile > 0) { diff --git a/Mage/src/main/java/mage/abilities/decorator/ConditionalOneShotEffect.java b/Mage/src/main/java/mage/abilities/decorator/ConditionalOneShotEffect.java index 59dd6d41ec0..cdd3ec99fd6 100644 --- a/Mage/src/main/java/mage/abilities/decorator/ConditionalOneShotEffect.java +++ b/Mage/src/main/java/mage/abilities/decorator/ConditionalOneShotEffect.java @@ -3,7 +3,6 @@ package mage.abilities.decorator; import mage.abilities.Ability; import mage.abilities.Mode; import mage.abilities.condition.Condition; -import mage.abilities.effects.Effect; import mage.abilities.effects.Effects; import mage.abilities.effects.OneShotEffect; import mage.constants.Outcome; @@ -126,10 +125,11 @@ public class ConditionalOneShotEffect extends OneShotEffect { } @Override - public Effect setTargetPointer(TargetPointer targetPointer) { + public ConditionalOneShotEffect setTargetPointer(TargetPointer targetPointer) { effects.setTargetPointer(targetPointer); otherwiseEffects.setTargetPointer(targetPointer); - return super.setTargetPointer(targetPointer); + super.setTargetPointer(targetPointer); + return this; } @Override diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/common/CreaturesDiedThisTurnCount.java b/Mage/src/main/java/mage/abilities/dynamicvalue/common/CreaturesDiedThisTurnCount.java index 016b5eb9f01..d5b3fb0b137 100644 --- a/Mage/src/main/java/mage/abilities/dynamicvalue/common/CreaturesDiedThisTurnCount.java +++ b/Mage/src/main/java/mage/abilities/dynamicvalue/common/CreaturesDiedThisTurnCount.java @@ -4,8 +4,6 @@ package mage.abilities.dynamicvalue.common; import mage.abilities.Ability; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.effects.Effect; -import mage.abilities.hint.Hint; -import mage.abilities.hint.ValueHint; import mage.game.Game; import mage.watchers.common.CreaturesDiedWatcher; @@ -15,12 +13,6 @@ import mage.watchers.common.CreaturesDiedWatcher; public enum CreaturesDiedThisTurnCount implements DynamicValue { instance; - private static final Hint hint = new ValueHint("Number of creatures that died this turn", instance); - - public static Hint getHint() { - return hint; - } - @Override public int calculate(Game game, Ability sourceAbility, Effect effect) { CreaturesDiedWatcher watcher = game.getState().getWatcher(CreaturesDiedWatcher.class); diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/common/GreatestToughnessAmongControlledCreaturesValue.java b/Mage/src/main/java/mage/abilities/dynamicvalue/common/GreatestToughnessAmongControlledCreaturesValue.java index 4ec730c0f08..edc372293ac 100644 --- a/Mage/src/main/java/mage/abilities/dynamicvalue/common/GreatestToughnessAmongControlledCreaturesValue.java +++ b/Mage/src/main/java/mage/abilities/dynamicvalue/common/GreatestToughnessAmongControlledCreaturesValue.java @@ -29,7 +29,7 @@ public enum GreatestToughnessAmongControlledCreaturesValue implements DynamicVal public int calculate(Game game, Ability sourceAbility, Effect effect) { return game .getBattlefield() - .getActivePermanents(filter, sourceAbility.getControllerId(), game) + .getActivePermanents(filter, sourceAbility.getControllerId(), sourceAbility, game) .stream() .map(MageObject::getToughness) .mapToInt(MageInt::getValue) diff --git a/Mage/src/main/java/mage/abilities/effects/CostModificationEffect.java b/Mage/src/main/java/mage/abilities/effects/CostModificationEffect.java index fa8f7b9b866..07412e7ed35 100644 --- a/Mage/src/main/java/mage/abilities/effects/CostModificationEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/CostModificationEffect.java @@ -21,6 +21,8 @@ public interface CostModificationEffect extends ContinuousEffect { /** * Called by the {@link ContinuousEffects#costModification(java.util.UUID, mage.abilities.Ability, mage.game.Game) ContinuousEffects.costModification} * method. + *

+ * Warning, choose dialogs restricted in plyable calculation, so you must check inCheckPlayableState * * @param game The game for which this effect should be applied. * @param source The source ability of this effect. diff --git a/Mage/src/main/java/mage/abilities/effects/Effects.java b/Mage/src/main/java/mage/abilities/effects/Effects.java index 2cdc4d54bc5..fbfdaf7ba5a 100644 --- a/Mage/src/main/java/mage/abilities/effects/Effects.java +++ b/Mage/src/main/java/mage/abilities/effects/Effects.java @@ -115,10 +115,18 @@ public class Effects extends ArrayList { sbText.append('.'); } + + if (mode.getCost() != null || mode.getFlavorWord() != null) { + sbText.replace(0, 1, sbText.substring(0, 1).toUpperCase()); + } + // cost + if (mode.getCost() != null) { + sbText.insert(0, " — "); + sbText.insert(0, mode.getCost().getText()); + } // flavor word if (mode.getFlavorWord() != null) { - return CardUtil.italicizeWithEmDash(mode.getFlavorWord()) - + CardUtil.getTextWithFirstCharUpperCase(sbText.toString()); + sbText.insert(0, CardUtil.italicizeWithEmDash(mode.getFlavorWord())); } return sbText.toString(); diff --git a/Mage/src/main/java/mage/abilities/effects/OneShotEffect.java b/Mage/src/main/java/mage/abilities/effects/OneShotEffect.java index 37725006a31..3463066e012 100644 --- a/Mage/src/main/java/mage/abilities/effects/OneShotEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/OneShotEffect.java @@ -31,7 +31,13 @@ public abstract class OneShotEffect extends EffectImpl { } @Override - public Effect setTargetPointer(TargetPointer targetPointer) { + public OneShotEffect concatBy(String concatPrefix) { + super.concatBy(concatPrefix); + return this; + } + + @Override + public OneShotEffect setTargetPointer(TargetPointer targetPointer) { super.setTargetPointer(targetPointer); return this; } 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 f3ac15bca08..78dc6e06d3c 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ChooseACardNameEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ChooseACardNameEffect.java @@ -50,14 +50,7 @@ public class ChooseACardNameEffect extends OneShotEffect { return nameSupplier.get(); } - public String getChoice(Game game, Ability source) { - return getChoice(game.getPlayer(source.getControllerId()), game, source, true); - } - - public String getChoice(Player player, Game game, Ability source, boolean setValue) { - if (player == null) { - return null; - } + public Choice makeChoiceObject() { Choice cardChoice = new ChoiceImpl(true, ChoiceHintType.CARD); Set names = this.getNames(); if (names.isEmpty()) { @@ -68,6 +61,18 @@ public class ChooseACardNameEffect extends OneShotEffect { cardChoice.setChoices(names); cardChoice.setMessage(CardUtil.getTextWithFirstCharUpperCase(this.getMessage())); cardChoice.clearChoice(); + return cardChoice; + } + + public String getChoice(Game game, Ability source) { + return getChoice(game.getPlayer(source.getControllerId()), game, source, true); + } + + public String getChoice(Player player, Game game, Ability source, boolean setValue) { + if (player == null) { + return null; + } + Choice cardChoice = makeChoiceObject(); player.choose(Outcome.Detriment, cardChoice, game); String cardName = cardChoice.getChoice(); if (cardName == null) { diff --git a/Mage/src/main/java/mage/abilities/effects/common/ChooseCreatureEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ChooseCreatureEffect.java index 654f839dc4c..a88e729a4da 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ChooseCreatureEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ChooseCreatureEffect.java @@ -13,27 +13,30 @@ import mage.target.TargetPermanent; import mage.util.CardUtil; /** - * To be used with AsEntersBattlefieldAbility (otherwise Zone Change Counter will be wrong) + * To be used with AsEntersBattlefieldAbility with useOffset=false (otherwise Zone Change Counter will be wrong) * * @author weirddan455 */ public class ChooseCreatureEffect extends OneShotEffect { private final FilterPermanent filter; + private final boolean useOffset; public ChooseCreatureEffect() { - this(StaticFilters.FILTER_ANOTHER_CREATURE_YOU_CONTROL); + this(StaticFilters.FILTER_ANOTHER_CREATURE_YOU_CONTROL, true); } - public ChooseCreatureEffect(FilterPermanent filter) { + public ChooseCreatureEffect(FilterPermanent filter, boolean useOffset) { super(Outcome.Benefit); this.filter = filter; this.staticText = "choose " + filter.getMessage(); + this.useOffset = useOffset; } private ChooseCreatureEffect(final ChooseCreatureEffect effect) { super(effect); this.filter = effect.filter; + this.useOffset = effect.useOffset; } @Override @@ -44,11 +47,8 @@ public class ChooseCreatureEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - if (controller == null) { - return false; - } Permanent sourcePermanent = game.getPermanentEntering(source.getSourceId()); - if (sourcePermanent == null) { + if (controller == null || sourcePermanent == null) { return false; } TargetPermanent target = new TargetPermanent(1, 1, filter, true); @@ -58,7 +58,10 @@ public class ChooseCreatureEffect extends OneShotEffect { return false; } game.getState().setValue( - CardUtil.getObjectZoneString("chosenCreature", sourcePermanent.getId(), game, sourcePermanent.getZoneChangeCounter(game) + 1, false), + CardUtil.getObjectZoneString( + "chosenCreature", sourcePermanent.getId(), game, + sourcePermanent.getZoneChangeCounter(game) + (useOffset ? 1 : 0), false + ), new MageObjectReference(chosenCreature, game) ); sourcePermanent.addInfo("chosen creature", CardUtil.addToolTipMarkTags("Chosen Creature " + chosenCreature.getIdName()), game); diff --git a/Mage/src/main/java/mage/abilities/effects/common/CounterUnlessPaysEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CounterUnlessPaysEffect.java index 59fce1a993d..fa5b1363f3a 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CounterUnlessPaysEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CounterUnlessPaysEffect.java @@ -89,8 +89,9 @@ public class CounterUnlessPaysEffect extends OneShotEffect { && costToPay.pay(source, game, source, spell.getControllerId(), false, null))) { game.informPlayers(player.getLogName() + " chooses not to pay " + costValueMessage + " to prevent the counter effect"); game.getStack().counter(spell.getId(), source, game, exile ? PutCards.EXILED : PutCards.GRAVEYARD); + } else { + game.informPlayers(player.getLogName() + " chooses to pay " + costValueMessage + " to prevent the counter effect"); } - game.informPlayers(player.getLogName() + " chooses to pay " + costValueMessage + " to prevent the counter effect"); return true; } diff --git a/Mage/src/main/java/mage/abilities/effects/common/CreateDelayedTriggeredAbilityEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CreateDelayedTriggeredAbilityEffect.java index fd9ed392471..8812762b3af 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CreateDelayedTriggeredAbilityEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CreateDelayedTriggeredAbilityEffect.java @@ -81,8 +81,9 @@ public class CreateDelayedTriggeredAbilityEffect extends OneShotEffect { } @Override - public Effect setTargetPointer(TargetPointer targetPointer) { + public CreateDelayedTriggeredAbilityEffect setTargetPointer(TargetPointer targetPointer) { ability.getEffects().setTargetPointer(targetPointer); - return super.setTargetPointer(targetPointer); + super.setTargetPointer(targetPointer); + return this; } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/CreateTokenAttachSourceEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CreateTokenAttachSourceEffect.java index 0d9c34e9c5b..11d45ac59f1 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CreateTokenAttachSourceEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CreateTokenAttachSourceEffect.java @@ -26,7 +26,7 @@ public class CreateTokenAttachSourceEffect extends CreateTokenEffect { public CreateTokenAttachSourceEffect(Token token, String innerConcat, boolean optional) { super(token); this.optional = optional; - staticText = staticText.concat(innerConcat + (optional ? " you may" : "") + " attach {this} to it"); + staticText = staticText.concat(innerConcat + (optional ? ". You may" : "") + " attach {this} to it"); } private CreateTokenAttachSourceEffect(final CreateTokenAttachSourceEffect effect) { diff --git a/Mage/src/main/java/mage/abilities/effects/common/CreateXXTokenExiledEffectManaValueEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CreateXXTokenExiledEffectManaValueEffect.java index 3b58617a27c..5166c9e6fb6 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CreateXXTokenExiledEffectManaValueEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CreateXXTokenExiledEffectManaValueEffect.java @@ -27,7 +27,7 @@ public class CreateXXTokenExiledEffectManaValueEffect extends OneShotEffect { super(Outcome.Benefit); this.tokenMaker = tokenMaker; staticText = "the exiled card's owner creates an X/X " + description + - "creature token, where X is the mana value of the exiled card"; + " creature token, where X is the mana value of the exiled card"; } private CreateXXTokenExiledEffectManaValueEffect(final CreateXXTokenExiledEffectManaValueEffect effect) { diff --git a/Mage/src/main/java/mage/abilities/effects/common/DevourEffect.java b/Mage/src/main/java/mage/abilities/effects/common/DevourEffect.java index 30cada36483..4eec8aa7048 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/DevourEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/DevourEffect.java @@ -142,7 +142,7 @@ public class DevourEffect extends ReplacementEffectImpl { text += devourFactor; } - text += " (As this enters, you may sacrifice any number of " + text += " (As this creature enters, you may sacrifice any number of " + filterMessage + "s. " + "This creature enters with "; diff --git a/Mage/src/main/java/mage/abilities/effects/common/DiscardOntoBattlefieldEffect.java b/Mage/src/main/java/mage/abilities/effects/common/DiscardOntoBattlefieldEffect.java index 60b50577bef..a97c1a1bf03 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/DiscardOntoBattlefieldEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/DiscardOntoBattlefieldEffect.java @@ -1,4 +1,3 @@ - package mage.abilities.effects.common; import mage.abilities.Ability; @@ -9,7 +8,6 @@ import mage.constants.Outcome; import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; import mage.game.events.ZoneChangeEvent; import mage.game.stack.StackObject; import mage.players.Player; @@ -21,7 +19,7 @@ public class DiscardOntoBattlefieldEffect extends ReplacementEffectImpl { public DiscardOntoBattlefieldEffect() { super(Duration.EndOfGame, Outcome.PutCardInPlay); - staticText = "If a spell or ability an opponent controls causes you to discard {this}, put it onto the battlefield instead of putting it into your graveyard"; + staticText = "If a spell or ability an opponent controls causes you to discard this card, put it onto the battlefield instead of putting it into your graveyard"; } protected DiscardOntoBattlefieldEffect(final DiscardOntoBattlefieldEffect effect) { @@ -40,30 +38,24 @@ public class DiscardOntoBattlefieldEffect extends ReplacementEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { - if (event.getTargetId().equals(source.getSourceId())) { - ZoneChangeEvent zcEvent = (ZoneChangeEvent) event; - if (zcEvent.getFromZone() == Zone.HAND && zcEvent.getToZone() == Zone.GRAVEYARD) { - StackObject spell = game.getStack().getStackObject(event.getSourceId()); - if (spell != null && game.getOpponents(source.getControllerId()).contains(spell.getControllerId())) { - return true; - } - } + if (!event.getTargetId().equals(source.getSourceId())) { + return false; } - return false; + ZoneChangeEvent zcEvent = (ZoneChangeEvent) event; + if (zcEvent.getFromZone() != Zone.HAND || zcEvent.getToZone() != Zone.GRAVEYARD) { + return false; + } + StackObject spell = game.getStack().getStackObject(event.getSourceId()); + return spell != null && game.getOpponents(source.getControllerId()).contains(spell.getControllerId()); } @Override public boolean replaceEvent(GameEvent event, Ability source, Game game) { Card card = game.getCard(source.getSourceId()); - if (card != null) { - Player owner = game.getPlayer(card.getOwnerId()); - if (owner != null) { - if (owner.moveCards(card, Zone.BATTLEFIELD, source, game)) { - return true; - } - } + if (card == null) { + return false; } - return false; + Player owner = game.getPlayer(card.getOwnerId()); + return owner != null && owner.moveCards(card, Zone.BATTLEFIELD, source, game); } - } diff --git a/Mage/src/main/java/mage/abilities/effects/common/DrawCardTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/DrawCardTargetEffect.java index baa6602d9de..210b127ae01 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/DrawCardTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/DrawCardTargetEffect.java @@ -64,7 +64,7 @@ public class DrawCardTargetEffect extends OneShotEffect { && player.canRespond()) { int cardsToDraw = amount.calculate(game, source, this); if (upTo) { - cardsToDraw = player.getAmount(0, cardsToDraw, "Draw how many cards?", game); + cardsToDraw = player.getAmount(0, cardsToDraw, "Draw how many cards?", source, game); } if (!optional || player.chooseUse(outcome, "Use draw effect?", source, game)) { diff --git a/Mage/src/main/java/mage/abilities/effects/common/ExileAdventureSpellEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ExileAdventureSpellEffect.java index 706af3cd62a..fa639b927f0 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ExileAdventureSpellEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ExileAdventureSpellEffect.java @@ -72,7 +72,7 @@ public class ExileAdventureSpellEffect extends OneShotEffect implements MageSing class AdventureCastFromExileEffect extends AsThoughEffectImpl { public AdventureCastFromExileEffect() { - super(AsThoughEffectType.CAST_FROM_NOT_OWN_HAND_ZONE, Duration.Custom, Outcome.Benefit); + super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.Custom, Outcome.Benefit); staticText = "Then exile this card. You may cast the creature later from exile."; } diff --git a/Mage/src/main/java/mage/abilities/effects/common/ExileSpellWithTimeCountersEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ExileSpellWithTimeCountersEffect.java index 95f9e58327a..6e63b969f58 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ExileSpellWithTimeCountersEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ExileSpellWithTimeCountersEffect.java @@ -23,7 +23,7 @@ public class ExileSpellWithTimeCountersEffect extends OneShotEffect { private final boolean gainsSuspend; public ExileSpellWithTimeCountersEffect(int counters) { - this (counters, false); + this(counters, false); } public ExileSpellWithTimeCountersEffect(int counters, boolean gainsSuspend) { @@ -33,6 +33,7 @@ public class ExileSpellWithTimeCountersEffect extends OneShotEffect { this.staticText = "exile {this} with " + CardUtil.numberToText(this.counters) + " time counters on it" + (gainsSuspend ? ". It gains suspend" : ""); } + private ExileSpellWithTimeCountersEffect(final ExileSpellWithTimeCountersEffect effect) { super(effect); this.counters = effect.counters; diff --git a/Mage/src/main/java/mage/abilities/effects/common/FaceVillainousChoiceOpponentsEffect.java b/Mage/src/main/java/mage/abilities/effects/common/FaceVillainousChoiceOpponentsEffect.java new file mode 100644 index 00000000000..17e802be8e1 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/FaceVillainousChoiceOpponentsEffect.java @@ -0,0 +1,45 @@ +package mage.abilities.effects.common; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.choices.FaceVillainousChoice; +import mage.constants.Outcome; +import mage.game.Game; +import mage.players.Player; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public class FaceVillainousChoiceOpponentsEffect extends OneShotEffect { + + private final FaceVillainousChoice choice; + + public FaceVillainousChoiceOpponentsEffect(FaceVillainousChoice choice) { + super(Outcome.Benefit); + this.choice = choice; + staticText = "each opponent " + choice.generateRule(); + } + + private FaceVillainousChoiceOpponentsEffect(final FaceVillainousChoiceOpponentsEffect effect) { + super(effect); + this.choice = effect.choice; + } + + @Override + public FaceVillainousChoiceOpponentsEffect copy() { + return new FaceVillainousChoiceOpponentsEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + for (UUID playerId : game.getOpponents(source.getControllerId())) { + Player player = game.getPlayer(playerId); + if (player != null) { + choice.faceChoice(player, game, source); + } + } + return true; + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/LockOrUnlockRoomTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/LockOrUnlockRoomTargetEffect.java new file mode 100644 index 00000000000..c0ee3efe42b --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/LockOrUnlockRoomTargetEffect.java @@ -0,0 +1,32 @@ +package mage.abilities.effects.common; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.constants.Outcome; +import mage.game.Game; + +/** + * @author TheElk801 + */ +public class LockOrUnlockRoomTargetEffect extends OneShotEffect { + + public LockOrUnlockRoomTargetEffect() { + super(Outcome.Benefit); + staticText = "lock or unlock a door of target Room you control"; + } + + private LockOrUnlockRoomTargetEffect(final LockOrUnlockRoomTargetEffect effect) { + super(effect); + } + + @Override + public LockOrUnlockRoomTargetEffect copy() { + return new LockOrUnlockRoomTargetEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + // TODO: Implement this + return true; + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/PutOnLibraryTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/PutOnLibraryTargetEffect.java index c980b60f011..43e5c36323b 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/PutOnLibraryTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/PutOnLibraryTargetEffect.java @@ -132,8 +132,8 @@ public class PutOnLibraryTargetEffect extends OneShotEffect { sb.append("put "); if (target.getMaxNumberOfTargets() == 0 || target.getMaxNumberOfTargets() == Integer.MAX_VALUE) { sb.append("any number of "); - } else if (target.getMaxNumberOfTargets() != 1 || target.getNumberOfTargets() != 1) { - if (target.getMaxNumberOfTargets() > target.getNumberOfTargets()) { + } else if (target.getMaxNumberOfTargets() != 1 || target.getMinNumberOfTargets() != 1) { + if (target.getMaxNumberOfTargets() > target.getMinNumberOfTargets()) { sb.append("up to "); } sb.append(CardUtil.numberToText(target.getMaxNumberOfTargets())).append(' '); diff --git a/Mage/src/main/java/mage/abilities/effects/common/ReturnFromGraveyardToBattlefieldTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ReturnFromGraveyardToBattlefieldTargetEffect.java index 4323e3b2115..152345b76e6 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ReturnFromGraveyardToBattlefieldTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ReturnFromGraveyardToBattlefieldTargetEffect.java @@ -89,7 +89,7 @@ public class ReturnFromGraveyardToBattlefieldTargetEffect extends OneShotEffect if (target.getMaxNumberOfTargets() == Integer.MAX_VALUE && target.getMinNumberOfTargets() == 0) { sb.append("any number of "); - } else if (target.getMaxNumberOfTargets() != target.getNumberOfTargets()) { + } else if (target.getMaxNumberOfTargets() != target.getMinNumberOfTargets()) { sb.append("up to "); sb.append(CardUtil.numberToText(target.getMaxNumberOfTargets())); sb.append(' '); diff --git a/Mage/src/main/java/mage/abilities/effects/common/RollDieWithResultTableEffect.java b/Mage/src/main/java/mage/abilities/effects/common/RollDieWithResultTableEffect.java index 762c1838f19..1ec72646208 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/RollDieWithResultTableEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/RollDieWithResultTableEffect.java @@ -153,10 +153,11 @@ public class RollDieWithResultTableEffect extends OneShotEffect { } @Override - public Effect setTargetPointer(TargetPointer targetPointer) { + public RollDieWithResultTableEffect setTargetPointer(TargetPointer targetPointer) { for (TableEntry tableEntry : resultsTable) { tableEntry.effects.setTargetPointer(targetPointer); } - return super.setTargetPointer(targetPointer); + super.setTargetPointer(targetPointer); + return this; } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/SacrificeAllEffect.java b/Mage/src/main/java/mage/abilities/effects/common/SacrificeAllEffect.java index d426ccf9627..62b51de5666 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/SacrificeAllEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/SacrificeAllEffect.java @@ -91,9 +91,7 @@ public class SacrificeAllEffect extends OneShotEffect { continue; } TargetSacrifice target = new TargetSacrifice(numTargets, filter); - while (!target.isChosen(game) && target.canChoose(player.getId(), source, game) && player.canRespond()) { - player.choose(Outcome.Sacrifice, target, source, game); - } + target.choose(Outcome.Sacrifice, player.getId(), source, game); perms.addAll(target.getTargets()); } diff --git a/Mage/src/main/java/mage/abilities/effects/common/SacrificeEffect.java b/Mage/src/main/java/mage/abilities/effects/common/SacrificeEffect.java index 034cff1cd46..c9a396d98b4 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/SacrificeEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/SacrificeEffect.java @@ -64,13 +64,12 @@ public class SacrificeEffect extends OneShotEffect { continue; } TargetSacrifice target = new TargetSacrifice(amount, filter); - while (!target.isChosen(game) && target.canChoose(player.getId(), source, game) && player.canRespond()) { - player.choose(Outcome.Sacrifice, target, source, game); - } - for (UUID targetId : target.getTargets()) { - Permanent permanent = game.getPermanent(targetId); - if (permanent != null && permanent.sacrifice(source, game)) { - applied = true; + if (target.choose(Outcome.Sacrifice, player.getId(), source.getSourceId(), source, game)) { + for (UUID targetId : target.getTargets()) { + Permanent permanent = game.getPermanent(targetId); + if (permanent != null && permanent.sacrifice(source, game)) { + applied = true; + } } } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/TransformSourceEffect.java b/Mage/src/main/java/mage/abilities/effects/common/TransformSourceEffect.java index 770ac3ae306..7d34ff2f7d5 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/TransformSourceEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/TransformSourceEffect.java @@ -12,8 +12,12 @@ import mage.game.permanent.Permanent; public class TransformSourceEffect extends OneShotEffect { public TransformSourceEffect() { + this(false); + } + + public TransformSourceEffect(boolean it) { super(Outcome.Transform); - staticText = "transform {this}"; + staticText = "transform " + (it ? "it" : "{this}"); } protected TransformSourceEffect(final TransformSourceEffect effect) { diff --git a/Mage/src/main/java/mage/abilities/effects/common/asthought/MayCastFromGraveyardAsAdventureEffect.java b/Mage/src/main/java/mage/abilities/effects/common/asthought/MayCastFromGraveyardAsAdventureEffect.java new file mode 100644 index 00000000000..78b3c80b95c --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/asthought/MayCastFromGraveyardAsAdventureEffect.java @@ -0,0 +1,54 @@ +package mage.abilities.effects.common.asthought; + +import mage.abilities.Ability; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.cards.Card; +import mage.constants.AsThoughEffectType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.Game; + +import java.util.UUID; + +/** + * @author Susucr + */ +public class MayCastFromGraveyardAsAdventureEffect extends AsThoughEffectImpl { + + public MayCastFromGraveyardAsAdventureEffect() { + super(AsThoughEffectType.CAST_ADVENTURE_FROM_NOT_OWN_HAND_ZONE, Duration.UntilEndOfYourNextTurn, Outcome.Benefit); + staticText = "you may cast it from your graveyard as an Adventure until the end of your next turn"; + } + + private MayCastFromGraveyardAsAdventureEffect(final MayCastFromGraveyardAsAdventureEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public MayCastFromGraveyardAsAdventureEffect copy() { + return new MayCastFromGraveyardAsAdventureEffect(this); + } + + @Override + public boolean applies(UUID sourceId, Ability source, UUID affectedControllerId, Game game) { + if (!source.isControlledBy(affectedControllerId)) { + return false; + } + Card card = game.getCard(sourceId); + if (card == null || card.getMainCard() == null || !card.getMainCard().getId().equals(source.getSourceId())) { + return false; + } + + Card sourceCard = game.getCard(source.getSourceId()); + + return sourceCard != null + && game.getState().getZone(source.getSourceId()) == Zone.GRAVEYARD + && source.getSourceObjectZoneChangeCounter() == sourceCard.getZoneChangeCounter(game); + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/combat/CanBlockAdditionalCreatureAllEffect.java b/Mage/src/main/java/mage/abilities/effects/common/combat/CanBlockAdditionalCreatureAllEffect.java index 1abd8033558..3e815aa31bb 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/combat/CanBlockAdditionalCreatureAllEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/combat/CanBlockAdditionalCreatureAllEffect.java @@ -45,7 +45,9 @@ public class CanBlockAdditionalCreatureAllEffect extends ContinuousEffectImpl { if (permanent != null) { // maxBlocks = 0 equals to "can block any number of creatures" if (amount > 0) { - permanent.setMaxBlocks(permanent.getMaxBlocks() + amount); + if (permanent.getMaxBlocks() > 0) { + permanent.setMaxBlocks(permanent.getMaxBlocks() + amount); + } } else { permanent.setMaxBlocks(0); } diff --git a/Mage/src/main/java/mage/abilities/effects/common/combat/CanBlockAdditionalCreatureEffect.java b/Mage/src/main/java/mage/abilities/effects/common/combat/CanBlockAdditionalCreatureEffect.java index 2eb7ea69efe..a2630e7b7e4 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/combat/CanBlockAdditionalCreatureEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/combat/CanBlockAdditionalCreatureEffect.java @@ -53,7 +53,9 @@ public class CanBlockAdditionalCreatureEffect extends ContinuousEffectImpl { if (permanent != null) { // maxBlocks = 0 equals to "can block any number of creatures" if (amount > 0) { - permanent.setMaxBlocks(permanent.getMaxBlocks() + amount); + if (permanent.getMaxBlocks() > 0) { + permanent.setMaxBlocks(permanent.getMaxBlocks() + amount); + } } else { permanent.setMaxBlocks(0); } diff --git a/Mage/src/main/java/mage/abilities/effects/common/combat/CanBlockAdditionalCreatureTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/combat/CanBlockAdditionalCreatureTargetEffect.java index b67f696c637..77011abb9a9 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/combat/CanBlockAdditionalCreatureTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/combat/CanBlockAdditionalCreatureTargetEffect.java @@ -53,7 +53,9 @@ public class CanBlockAdditionalCreatureTargetEffect extends ContinuousEffectImpl // maxBlocks = 0 equals to "can block any number of creatures" if (amount > 0) { - target.setMaxBlocks(target.getMaxBlocks() + amount); + if (target.getMaxBlocks() > 0) { + target.setMaxBlocks(target.getMaxBlocks() + amount); + } } else { target.setMaxBlocks(0); } diff --git a/Mage/src/main/java/mage/abilities/effects/common/combat/GoadTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/combat/GoadTargetEffect.java index dc944f0add3..4fe12b5667b 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/combat/GoadTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/combat/GoadTargetEffect.java @@ -33,7 +33,7 @@ public class GoadTargetEffect extends ContinuousEffectImpl { super(duration, Layer.RulesEffects, SubLayer.NA, Outcome.Detriment); } - private GoadTargetEffect(final GoadTargetEffect effect) { + protected GoadTargetEffect(final GoadTargetEffect effect) { super(effect); } diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityControlledEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityControlledEffect.java index 49fae99cdcd..2f05691c6dd 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityControlledEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityControlledEffect.java @@ -9,7 +9,6 @@ import mage.constants.Layer; import mage.constants.Outcome; import mage.constants.SubLayer; import mage.filter.FilterPermanent; -import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; import mage.util.CardUtil; @@ -27,10 +26,6 @@ public class GainAbilityControlledEffect extends ContinuousEffectImpl { protected boolean forceQuotes = false; protected boolean durationRuleAtStart = false; // put duration rule to the start of the rules instead end - public GainAbilityControlledEffect(Ability ability, Duration duration) { - this(ability, duration, StaticFilters.FILTER_PERMANENTS); - } - public GainAbilityControlledEffect(Ability ability, Duration duration, FilterPermanent filter) { this(ability, duration, filter, false); } diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAnchorWordAbilitySourceEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAnchorWordAbilitySourceEffect.java index f4af6963691..2106accaa73 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAnchorWordAbilitySourceEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAnchorWordAbilitySourceEffect.java @@ -7,6 +7,7 @@ import mage.abilities.effects.Effect; import mage.constants.*; import mage.game.Game; import mage.game.permanent.Permanent; +import mage.util.CardUtil; /** * @author TheElk801 @@ -22,7 +23,7 @@ public class GainAnchorWordAbilitySourceEffect extends ContinuousEffectImpl { public GainAnchorWordAbilitySourceEffect(Ability ability, ModeChoice modeChoice) { super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); - this.staticText = "&bull " + modeChoice + " — " + ability.getRule(); + this.staticText = "&bull " + modeChoice + " — " + CardUtil.getTextWithFirstCharUpperCase(ability.getRule()); this.ability = ability; this.modeChoice = modeChoice; this.ability.setRuleVisible(false); diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/SetBasePowerToughnessEnchantedEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/SetBasePowerToughnessAttachedEffect.java similarity index 62% rename from Mage/src/main/java/mage/abilities/effects/common/continuous/SetBasePowerToughnessEnchantedEffect.java rename to Mage/src/main/java/mage/abilities/effects/common/continuous/SetBasePowerToughnessAttachedEffect.java index b71ebe4eda4..94dbe5d188d 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/SetBasePowerToughnessEnchantedEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/SetBasePowerToughnessAttachedEffect.java @@ -2,41 +2,34 @@ package mage.abilities.effects.common.continuous; import mage.abilities.Ability; import mage.abilities.effects.ContinuousEffectImpl; -import mage.constants.Duration; -import mage.constants.Layer; -import mage.constants.Outcome; -import mage.constants.SubLayer; +import mage.constants.*; import mage.game.Game; import mage.game.permanent.Permanent; /** * @author LevelX2 */ -public class SetBasePowerToughnessEnchantedEffect extends ContinuousEffectImpl { +public class SetBasePowerToughnessAttachedEffect extends ContinuousEffectImpl { private final int power; private final int toughness; - public SetBasePowerToughnessEnchantedEffect() { - this(0, 2); - } - - public SetBasePowerToughnessEnchantedEffect(int power, int toughness) { + public SetBasePowerToughnessAttachedEffect(int power, int toughness, AttachmentType attachmentType) { super(Duration.WhileOnBattlefield, Layer.PTChangingEffects_7, SubLayer.SetPT_7b, Outcome.BoostCreature); - staticText = "Enchanted creature has base power and toughness " + power + "/" + toughness; + staticText = attachmentType.verb() + " creature has base power and toughness " + power + "/" + toughness; this.power = power; this.toughness = toughness; } - protected SetBasePowerToughnessEnchantedEffect(final SetBasePowerToughnessEnchantedEffect effect) { + protected SetBasePowerToughnessAttachedEffect(final SetBasePowerToughnessAttachedEffect effect) { super(effect); this.power = effect.power; this.toughness = effect.toughness; } @Override - public SetBasePowerToughnessEnchantedEffect copy() { - return new SetBasePowerToughnessEnchantedEffect(this); + public SetBasePowerToughnessAttachedEffect copy() { + return new SetBasePowerToughnessAttachedEffect(this); } @Override diff --git a/Mage/src/main/java/mage/abilities/effects/common/replacement/GraveyardFromAnywhereExileReplacementEffect.java b/Mage/src/main/java/mage/abilities/effects/common/replacement/GraveyardFromAnywhereExileReplacementEffect.java index 979c26e60a6..36c1366d748 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/replacement/GraveyardFromAnywhereExileReplacementEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/replacement/GraveyardFromAnywhereExileReplacementEffect.java @@ -35,6 +35,7 @@ public class GraveyardFromAnywhereExileReplacementEffect extends ReplacementEffe public GraveyardFromAnywhereExileReplacementEffect(Duration duration) { this(duration, StaticFilters.FILTER_CARD_A, true, false); } + protected GraveyardFromAnywhereExileReplacementEffect(Duration duration, FilterCard filter, boolean onlyYou, boolean tokens) { super(duration, Outcome.Exile); this.filter = filter; @@ -43,7 +44,7 @@ public class GraveyardFromAnywhereExileReplacementEffect extends ReplacementEffe this.setText(); } - private GraveyardFromAnywhereExileReplacementEffect(final GraveyardFromAnywhereExileReplacementEffect effect) { + protected GraveyardFromAnywhereExileReplacementEffect(final GraveyardFromAnywhereExileReplacementEffect effect) { super(effect); this.filter = effect.filter; this.onlyYou = effect.onlyYou; diff --git a/Mage/src/main/java/mage/abilities/effects/common/ruleModifying/PlayFromGraveyardControllerEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ruleModifying/PlayFromGraveyardControllerEffect.java index 3cd6d1a22c7..1a7aef2a193 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ruleModifying/PlayFromGraveyardControllerEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ruleModifying/PlayFromGraveyardControllerEffect.java @@ -19,11 +19,19 @@ import java.util.UUID; */ public class PlayFromGraveyardControllerEffect extends AsThoughEffectImpl { + private static final FilterCard filterPlayCards = new FilterCard("cards"); private static final FilterCard filterPlayLands = new FilterLandCard("lands"); private static final FilterCard filterPlayCast = new FilterCard("play lands and cast spells"); private final FilterCard filter; + /** + * You may play cards from your graveyard. + */ + public static PlayFromGraveyardControllerEffect playCards() { + return new PlayFromGraveyardControllerEffect(filterPlayCards); + } + /** * You may play lands from your graveyard. */ @@ -53,7 +61,7 @@ public class PlayFromGraveyardControllerEffect extends AsThoughEffectImpl { this.filter = filter; String filterMessage = filter.getMessage(); if (!filterMessage.startsWith("play ") && !filterMessage.startsWith("cast")) { - if (filterMessage.contains("lands")) { + if (filterMessage.contains("cards") || filterMessage.contains("lands")) { filterMessage = "play " + filterMessage; } else { filterMessage = "cast " + filterMessage; 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 8f7a28686c8..33af7b61be5 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 @@ -87,7 +87,7 @@ public class SearchLibraryPutInHandOrOnBattlefieldEffect extends SearchEffect { private void setText() { StringBuilder sb = new StringBuilder(); sb.append("search your library for "); - if (target.getNumberOfTargets() == 0 && target.getMaxNumberOfTargets() > 0) { + if (target.getMinNumberOfTargets() == 0 && target.getMaxNumberOfTargets() > 0) { sb.append("up to ").append(CardUtil.numberToText(target.getMaxNumberOfTargets())).append(' '); sb.append(target.getTargetName()).append(revealCards ? ", reveal them," : "").append(" and put them into your hand"); } else { diff --git a/Mage/src/main/java/mage/abilities/effects/mana/AddManaFromColorChoicesEffect.java b/Mage/src/main/java/mage/abilities/effects/mana/AddManaFromColorChoicesEffect.java index 709bf7f0d6d..6f40375b9b7 100644 --- a/Mage/src/main/java/mage/abilities/effects/mana/AddManaFromColorChoicesEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/mana/AddManaFromColorChoicesEffect.java @@ -32,7 +32,7 @@ public class AddManaFromColorChoicesEffect extends ManaEffect { .map(Mana::new) .forEach(netMana::add); staticText = "add " + CardUtil - .concatWithOr(this.netMana.stream().map(s -> "{" + s + '}').collect(Collectors.toList())); + .concatWithOr(this.netMana.stream().map(Mana::toString).collect(Collectors.toList())); } private AddManaFromColorChoicesEffect(final AddManaFromColorChoicesEffect effect) { diff --git a/Mage/src/main/java/mage/abilities/hint/ConditionHint.java b/Mage/src/main/java/mage/abilities/hint/ConditionHint.java index 2cdac23c408..52d7847477f 100644 --- a/Mage/src/main/java/mage/abilities/hint/ConditionHint.java +++ b/Mage/src/main/java/mage/abilities/hint/ConditionHint.java @@ -20,7 +20,7 @@ public class ConditionHint implements Hint { private final boolean useIcons; public ConditionHint(Condition condition) { - this(condition, condition.toString()); + this(condition, CardUtil.getTextWithFirstCharUpperCase(condition.toString())); } public ConditionHint(Condition condition, String textWithIcons) { diff --git a/Mage/src/main/java/mage/abilities/keyword/AssistAbility.java b/Mage/src/main/java/mage/abilities/keyword/AssistAbility.java index 295361da40c..bb2e45721c7 100644 --- a/Mage/src/main/java/mage/abilities/keyword/AssistAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/AssistAbility.java @@ -173,8 +173,8 @@ class AssistEffect extends OneShotEffect { // AI can't assist other players, maybe for teammates only (but tests must work as normal) int amountToPay = 0; if (!targetPlayer.isComputer()) { - amountToPay = targetPlayer.announceXMana(0, unpaid.getMana().getGeneric(), - "How much mana to pay as assist for " + controller.getName() + "?", game, source); + amountToPay = targetPlayer.announceX(0, unpaid.getMana().getGeneric(), + "How much mana to pay as assist for " + controller.getName() + "?", game, source, true); } if (amountToPay > 0) { diff --git a/Mage/src/main/java/mage/abilities/keyword/JobSelectAbility.java b/Mage/src/main/java/mage/abilities/keyword/JobSelectAbility.java new file mode 100644 index 00000000000..16a42d86d99 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/keyword/JobSelectAbility.java @@ -0,0 +1,30 @@ +package mage.abilities.keyword; + +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.CreateTokenAttachSourceEffect; +import mage.game.permanent.token.HeroToken; + +/** + * @author balazskristof + */ +public class JobSelectAbility extends EntersBattlefieldTriggeredAbility { + + public JobSelectAbility() { + super(new CreateTokenAttachSourceEffect(new HeroToken())); + } + + protected JobSelectAbility(final JobSelectAbility ability) { + super(ability); + } + + @Override + public String getRule() { + return "Job select (When this Equipment enters, " + + "create a 1/1 colorless Hero creature token, then attach this to it.)"; + } + + @Override + public JobSelectAbility copy() { + return new JobSelectAbility(this); + } +} diff --git a/Mage/src/main/java/mage/abilities/keyword/LeylineAbility.java b/Mage/src/main/java/mage/abilities/keyword/LeylineAbility.java index 1fc20d2d029..4fb2706e010 100644 --- a/Mage/src/main/java/mage/abilities/keyword/LeylineAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/LeylineAbility.java @@ -1,7 +1,5 @@ - package mage.abilities.keyword; -import java.io.ObjectStreamException; import mage.abilities.MageSingleton; import mage.abilities.OpeningHandAction; import mage.abilities.StaticAbility; @@ -11,8 +9,9 @@ import mage.constants.Zone; import mage.game.Game; import mage.players.Player; +import java.io.ObjectStreamException; + /** - * * @author BetaSteward_at_googlemail.com */ public class LeylineAbility extends StaticAbility implements MageSingleton, OpeningHandAction { @@ -33,7 +32,7 @@ public class LeylineAbility extends StaticAbility implements MageSingleton, Open @Override public String getRule() { - return "If {this} is in your opening hand, you may begin the game with it on the battlefield."; + return "If this card is in your opening hand, you may begin the game with it on the battlefield."; } @Override diff --git a/Mage/src/main/java/mage/abilities/keyword/ReadAheadAbility.java b/Mage/src/main/java/mage/abilities/keyword/ReadAheadAbility.java new file mode 100644 index 00000000000..0fe24e3d801 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/keyword/ReadAheadAbility.java @@ -0,0 +1,43 @@ +package mage.abilities.keyword; + +import mage.abilities.MageSingleton; +import mage.abilities.StaticAbility; +import mage.constants.Zone; + +import java.io.ObjectStreamException; + +/** + * @author TheElk801 + */ +public class ReadAheadAbility extends StaticAbility implements MageSingleton { + + private static final ReadAheadAbility instance; + + static { + instance = new ReadAheadAbility(); + } + + private Object readResolve() throws ObjectStreamException { + return instance; + } + + public static ReadAheadAbility getInstance() { + return instance; + } + + private ReadAheadAbility() { + super(Zone.BATTLEFIELD, null); + this.setRuleAtTheTop(true); + } + + @Override + public String getRule() { + return "read ahead"; + } + + @Override + public ReadAheadAbility copy() { + return instance; + } + +} diff --git a/Mage/src/main/java/mage/abilities/keyword/SuspendAbility.java b/Mage/src/main/java/mage/abilities/keyword/SuspendAbility.java index 1114ecebc64..86b584fddf3 100644 --- a/Mage/src/main/java/mage/abilities/keyword/SuspendAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/SuspendAbility.java @@ -1,10 +1,10 @@ package mage.abilities.keyword; import mage.MageIdentifier; +import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.SpecialAction; import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.abilities.condition.common.SuspendedCondition; import mage.abilities.costs.VariableCostType; import mage.abilities.costs.mana.ManaCost; @@ -14,7 +14,9 @@ import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.GainSuspendEffect; import mage.abilities.effects.common.counter.RemoveCounterSourceEffect; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.cards.Card; import mage.cards.CardsImpl; import mage.cards.ModalDoubleFacedCard; @@ -230,6 +232,34 @@ public class SuspendAbility extends SpecialAction { return super.canActivate(playerId, game); } + public static boolean addTimeCountersAndSuspend(Card card, int amount, Ability source, Game game) { + if (card == null || card.isCopy()) { + return false; + } + if (!Zone.EXILED.match(game.getState().getZone(card.getId()))) { + return false; + } + Player owner = game.getPlayer(card.getOwnerId()); + if (owner == null) { + return false; + } + game.getExile().moveToAnotherZone( + card.getMainCard(), game, + game.getExile().createZone( + SuspendAbility.getSuspendExileId(owner.getId(), game), + "Suspended cards of " + owner.getName() + ) + ); + if (amount > 0) { + card.addCounters(CounterType.TIME.createInstance(amount), owner.getId(), source, game); + } + if (!card.getAbilities(game).containsClass(SuspendAbility.class)) { + game.addEffect(new GainSuspendEffect(new MageObjectReference(card, game)), source); + } + game.informPlayers(owner.getLogName() + " suspends " + amount + " - " + card.getName()); + return true; + } + @Override public String getRule() { return ruleText; diff --git a/Mage/src/main/java/mage/abilities/keyword/TieredAbility.java b/Mage/src/main/java/mage/abilities/keyword/TieredAbility.java new file mode 100644 index 00000000000..465102f2c2f --- /dev/null +++ b/Mage/src/main/java/mage/abilities/keyword/TieredAbility.java @@ -0,0 +1,26 @@ +package mage.abilities.keyword; + +import mage.abilities.StaticAbility; +import mage.cards.Card; +import mage.constants.Zone; + +/** + * @author TheElk801 + */ +public class TieredAbility extends StaticAbility { + + public TieredAbility(Card card) { + super(Zone.ALL, null); + this.setRuleVisible(false); + card.getSpellAbility().getModes().setChooseText("Tiered (Choose one additional cost.)"); + } + + private TieredAbility(final TieredAbility ability) { + super(ability); + } + + @Override + public TieredAbility copy() { + return new TieredAbility(this); + } +} diff --git a/Mage/src/main/java/mage/abilities/mana/ManaAbility.java b/Mage/src/main/java/mage/abilities/mana/ManaAbility.java index 94e7a221a42..38a567fabe9 100644 --- a/Mage/src/main/java/mage/abilities/mana/ManaAbility.java +++ b/Mage/src/main/java/mage/abilities/mana/ManaAbility.java @@ -1,13 +1,13 @@ package mage.abilities.mana; -import java.util.List; -import java.util.Set; import mage.Mana; import mage.constants.ManaType; import mage.game.Game; +import java.util.List; +import java.util.Set; + /** - * * @author LevelX2 */ public interface ManaAbility { @@ -20,7 +20,7 @@ public interface ManaAbility { * @return */ List getNetMana(Game game); - + /** * Used to check the possible mana production to determine which spells * and/or abilities can be used. (player.getPlayable()). @@ -28,7 +28,7 @@ public interface ManaAbility { * * @param game * @param possibleManaInPool The possible mana already produced by other sources for this calculation option - * @return + * @return */ List getNetMana(Game game, Mana possibleManaInPool); @@ -60,7 +60,12 @@ public interface ManaAbility { * @return */ boolean isPoolDependant(); - + + /** + * How many more times can this ability be activated this turn + */ + int getMaxMoreActivationsThisTurn(Game game); + ManaAbility setPoolDependant(boolean pooleDependant); - + } diff --git a/Mage/src/main/java/mage/abilities/mana/ManaOptions.java b/Mage/src/main/java/mage/abilities/mana/ManaOptions.java index 02a214d1d55..000e610435e 100644 --- a/Mage/src/main/java/mage/abilities/mana/ManaOptions.java +++ b/Mage/src/main/java/mage/abilities/mana/ManaOptions.java @@ -393,6 +393,7 @@ public class ManaOptions extends LinkedHashSet { && onlyManaCosts && manaToAdd.countColored() > 0; boolean canHaveBetterValues; + int maxRepeat = manaAbility.getMaxMoreActivationsThisTurn(game); Mana possibleMana = new Mana(); Mana improvedMana = new Mana(); @@ -401,9 +402,11 @@ public class ManaOptions extends LinkedHashSet { // example: {G}: Add one mana of any color for (Mana possiblePay : ManaOptions.getPossiblePayCombinations(cost, startingMana)) { improvedMana.setToMana(startingMana); + int currentAttempt = 0; do { // loop until all mana replaced by better values canHaveBetterValues = false; + currentAttempt++; // it's impossible to analyse all payment order (pay {R} for {1}, {Any} for {G}, etc) // so use simple cost simulation by subtract @@ -441,7 +444,7 @@ public class ManaOptions extends LinkedHashSet { } improvedMana.setToMana(possibleMana); } - } while (repeatable && canHaveBetterValues && improvedMana.includesMana(possiblePay)); + } while (repeatable && (currentAttempt < maxRepeat) && canHaveBetterValues && improvedMana.includesMana(possiblePay)); } return oldManaWasReplaced; } @@ -670,12 +673,14 @@ final class Comparators { for (T first : elements) { for (T second : elements) { int firstGreaterThanSecond = comparator.compare(first, second); - if (firstGreaterThanSecond <= 0) + if (firstGreaterThanSecond <= 0) { continue; + } for (T third : elements) { int secondGreaterThanThird = comparator.compare(second, third); - if (secondGreaterThanThird <= 0) + if (secondGreaterThanThird <= 0) { continue; + } int firstGreaterThanThird = comparator.compare(first, third); if (firstGreaterThanThird <= 0) { // Uncomment the following line to step through the failed case diff --git a/Mage/src/main/java/mage/abilities/mana/TriggeredManaAbility.java b/Mage/src/main/java/mage/abilities/mana/TriggeredManaAbility.java index ca425dbfbcc..827c853f713 100644 --- a/Mage/src/main/java/mage/abilities/mana/TriggeredManaAbility.java +++ b/Mage/src/main/java/mage/abilities/mana/TriggeredManaAbility.java @@ -104,6 +104,11 @@ public abstract class TriggeredManaAbility extends TriggeredAbilityImpl implemen return poolDependant; } + @Override + public int getMaxMoreActivationsThisTurn(Game game) { + return getRemainingTriggersLimitEachTurn(game); + } + @Override public TriggeredManaAbility setPoolDependant(boolean poolDependant) { this.poolDependant = poolDependant; diff --git a/Mage/src/main/java/mage/abilities/token/FoodAbility.java b/Mage/src/main/java/mage/abilities/token/FoodAbility.java index ce58c9c0259..122ba2eee46 100644 --- a/Mage/src/main/java/mage/abilities/token/FoodAbility.java +++ b/Mage/src/main/java/mage/abilities/token/FoodAbility.java @@ -9,11 +9,12 @@ import mage.constants.Zone; public class FoodAbility extends ActivatedAbilityImpl { - public FoodAbility(boolean named) { + public FoodAbility() { super(Zone.BATTLEFIELD, new GainLifeEffect(3), new GenericManaCost(2)); + // {2}, {T}, Sacrifice this artifact: You gain 3 life.” this.addCost(new TapSourceCost()); - this.addCost(new SacrificeSourceCost().setText("sacrifice " + (named ? "{this}" : "this artifact"))); + this.addCost(new SacrificeSourceCost()); } private FoodAbility(final FoodAbility ability) { @@ -24,5 +25,5 @@ public class FoodAbility extends ActivatedAbilityImpl { public FoodAbility copy() { return new FoodAbility(this); } - + } diff --git a/Mage/src/main/java/mage/cards/AdventureSpellCard.java b/Mage/src/main/java/mage/cards/AdventureSpellCard.java index c2c2a45efcb..1b372db7415 100644 --- a/Mage/src/main/java/mage/cards/AdventureSpellCard.java +++ b/Mage/src/main/java/mage/cards/AdventureSpellCard.java @@ -9,6 +9,7 @@ import mage.constants.SubType; import mage.constants.Zone; import mage.game.ExileZone; import mage.game.Game; +import mage.util.CardUtil; import java.util.Arrays; import java.util.List; @@ -166,7 +167,7 @@ class AdventureCardSpellAbility extends SpellAbility { + " " + getManaCosts().getText() + " — " - + super.getRule(false) // without cost + + CardUtil.getTextWithFirstCharUpperCase(super.getRule(false)) // without cost + " (Then exile this card. You may cast the creature later from exile.)"; } diff --git a/Mage/src/main/java/mage/cards/ArtRect.java b/Mage/src/main/java/mage/cards/ArtRect.java index ceb6e6b0b8e..39faef953e9 100644 --- a/Mage/src/main/java/mage/cards/ArtRect.java +++ b/Mage/src/main/java/mage/cards/ArtRect.java @@ -7,7 +7,7 @@ import java.awt.geom.Rectangle2D; */ public enum ArtRect { NORMAL(new Rectangle2D.Double(.079f, .11f, .84f, .42f)), - RETRO(new Rectangle2D.Double(.12f, .11f, .77f, .43f)), + RETRO(new Rectangle2D.Double(.12f, .11f, .76f, .43f)), AFTERMATH_TOP(new Rectangle2D.Double(0.075, 0.113, 0.832, 0.227)), AFTERMATH_BOTTOM(new Rectangle2D.Double(0.546, 0.562, 0.272, 0.346)), SPLIT_LEFT(new Rectangle2D.Double(0.152, 0.539, 0.386, 0.400)), diff --git a/Mage/src/main/java/mage/cards/Card.java b/Mage/src/main/java/mage/cards/Card.java index c8547e513a6..21776e6be3a 100644 --- a/Mage/src/main/java/mage/cards/Card.java +++ b/Mage/src/main/java/mage/cards/Card.java @@ -82,7 +82,7 @@ public interface Card extends MageObject, Ownerable { return null; } - default Card getMeldsToCard() { + default MeldCard getMeldsToCard() { return null; } diff --git a/Mage/src/main/java/mage/cards/CardImpl.java b/Mage/src/main/java/mage/cards/CardImpl.java index 51bc39e6676..27d65fe059f 100644 --- a/Mage/src/main/java/mage/cards/CardImpl.java +++ b/Mage/src/main/java/mage/cards/CardImpl.java @@ -45,8 +45,8 @@ public abstract class CardImpl extends MageObjectImpl implements Card { protected Rarity rarity; protected Class secondSideCardClazz; protected Class meldsWithClazz; - protected Class meldsToClazz; - protected Card meldsToCard; + protected Class meldsToClazz; + protected MeldCard meldsToCard; protected Card secondSideCard; protected boolean nightCard; protected SpellAbility spellAbility; @@ -708,14 +708,14 @@ public abstract class CardImpl extends MageObjectImpl implements Card { } @Override - public Card getMeldsToCard() { + public MeldCard getMeldsToCard() { // init card on first call if (meldsToClazz == null && meldsToCard == null) { return null; } if (meldsToCard == null) { - meldsToCard = initSecondSideCard(meldsToClazz); + meldsToCard = (MeldCard) initSecondSideCard(meldsToClazz); } return meldsToCard; @@ -802,7 +802,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card { game.fireEvent(addedOneEvent); } else { finalAmount--; - returnCode = false; + returnCode = false; // restricted by ADD_COUNTER } } if (finalAmount > 0) { @@ -810,10 +810,15 @@ public abstract class CardImpl extends MageObjectImpl implements Card { addedAllEvent.setFlag(isEffectFlag); game.fireEvent(addedAllEvent); } else { + // TODO: must return true, cause it's not replaced here (rework Fangs of Kalonia and Spectacular Showdown) + // example from Devoted Druid + // If you can put counters on it, but that is modified by an effect (such as that of Vizier of Remedies), + // you can activate the ability even if paying the cost causes no counters to be put on Devoted Druid. + // (2018-12-07) returnCode = false; } } else { - returnCode = false; + returnCode = false; // restricted by ADD_COUNTERS } return returnCode; } diff --git a/Mage/src/main/java/mage/cards/OmenSpellCard.java b/Mage/src/main/java/mage/cards/OmenSpellCard.java index 04fcdd5d725..49181b0a38c 100644 --- a/Mage/src/main/java/mage/cards/OmenSpellCard.java +++ b/Mage/src/main/java/mage/cards/OmenSpellCard.java @@ -9,6 +9,7 @@ import mage.constants.SpellAbilityType; import mage.constants.SubType; import mage.constants.Zone; import mage.game.Game; +import mage.util.CardUtil; import java.util.Arrays; import java.util.List; @@ -151,7 +152,7 @@ class OmenCardSpellAbility extends SpellAbility { + " " + getManaCosts().getText() + " — " - + super.getRule(false) // without cost + + CardUtil.getTextWithFirstCharUpperCase(super.getRule(false)) // without cost + " (Then shuffle this card into its owner's library.)"; } diff --git a/Mage/src/main/java/mage/cards/decks/DeckValidator.java b/Mage/src/main/java/mage/cards/decks/DeckValidator.java index 71f5f1ecafb..6a9772c4ae5 100644 --- a/Mage/src/main/java/mage/cards/decks/DeckValidator.java +++ b/Mage/src/main/java/mage/cards/decks/DeckValidator.java @@ -50,6 +50,7 @@ public abstract class DeckValidator implements Serializable { maxCopiesMap.put("Templar Knight", Integer.MAX_VALUE); maxCopiesMap.put("Hare Apparent", Integer.MAX_VALUE); maxCopiesMap.put("Tempest Hawk", Integer.MAX_VALUE); + maxCopiesMap.put("Cid, Timeless Artificer", Integer.MAX_VALUE); maxCopiesMap.put("Once More with Feeling", 1); maxCopiesMap.put("Seven Dwarves", 7); maxCopiesMap.put("Nazgul", 9); diff --git a/Mage/src/main/java/mage/cards/repository/CardRepository.java b/Mage/src/main/java/mage/cards/repository/CardRepository.java index 88cb2fbadda..9f6e39d6a75 100644 --- a/Mage/src/main/java/mage/cards/repository/CardRepository.java +++ b/Mage/src/main/java/mage/cards/repository/CardRepository.java @@ -43,6 +43,9 @@ public enum CardRepository { private Dao cardsDao; + // store names lists like all cards, lands, etc (it's static data and can be calculated one time only) + private static final Map> namesQueryCache = new HashMap<>(); + // sets with exclusively snow basics public static final Set snowLandSetCodes = new HashSet<>(Arrays.asList( "CSP", @@ -156,8 +159,11 @@ public enum CardRepository { return snowLandSetCodes.contains(setCode); } - public Set getNames() { - Set names = new TreeSet<>(); + public synchronized Set getNames() { + Set names = namesQueryCache.computeIfAbsent("getNames", x -> new TreeSet<>()); + if (!names.isEmpty()) { + return names; + } try { QueryBuilder qb = cardsDao.queryBuilder(); qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName"); @@ -172,8 +178,11 @@ public enum CardRepository { return names; } - public Set getNonLandNames() { - Set names = new TreeSet<>(); + public synchronized Set getNonLandNames() { + Set names = namesQueryCache.computeIfAbsent("getNonLandNames", x -> new TreeSet<>()); + if (!names.isEmpty()) { + return names; + } try { QueryBuilder qb = cardsDao.queryBuilder(); qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName"); @@ -189,8 +198,11 @@ public enum CardRepository { return names; } - public Set getNonbasicLandNames() { - Set names = new TreeSet<>(); + public synchronized Set getNonbasicLandNames() { + Set names = namesQueryCache.computeIfAbsent("getNonbasicLandNames", x -> new TreeSet<>()); + if (!names.isEmpty()) { + return names; + } try { QueryBuilder qb = cardsDao.queryBuilder(); qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName"); @@ -210,8 +222,11 @@ public enum CardRepository { return names; } - public Set getNotBasicLandNames() { - Set names = new TreeSet<>(); + public synchronized Set getNotBasicLandNames() { + Set names = namesQueryCache.computeIfAbsent("getNotBasicLandNames", x -> new TreeSet<>()); + if (!names.isEmpty()) { + return names; + } try { QueryBuilder qb = cardsDao.queryBuilder(); qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName"); @@ -227,8 +242,11 @@ public enum CardRepository { return names; } - public Set getCreatureNames() { - Set names = new TreeSet<>(); + public synchronized Set getCreatureNames() { + Set names = namesQueryCache.computeIfAbsent("getCreatureNames", x -> new TreeSet<>()); + if (!names.isEmpty()) { + return names; + } try { QueryBuilder qb = cardsDao.queryBuilder(); qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName"); @@ -244,8 +262,11 @@ public enum CardRepository { return names; } - public Set getArtifactNames() { - Set names = new TreeSet<>(); + public synchronized Set getArtifactNames() { + Set names = namesQueryCache.computeIfAbsent("getArtifactNames", x -> new TreeSet<>()); + if (!names.isEmpty()) { + return names; + } try { QueryBuilder qb = cardsDao.queryBuilder(); qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName"); @@ -261,8 +282,11 @@ public enum CardRepository { return names; } - public Set getNonLandAndNonCreatureNames() { - Set names = new TreeSet<>(); + public synchronized Set getNonLandAndNonCreatureNames() { + Set names = namesQueryCache.computeIfAbsent("getNonLandAndNonCreatureNames", x -> new TreeSet<>()); + if (!names.isEmpty()) { + return names; + } try { QueryBuilder qb = cardsDao.queryBuilder(); qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName"); @@ -282,8 +306,11 @@ public enum CardRepository { return names; } - public Set getNonArtifactAndNonLandNames() { - Set names = new TreeSet<>(); + public synchronized Set getNonArtifactAndNonLandNames() { + Set names = namesQueryCache.computeIfAbsent("getNonArtifactAndNonLandNames", x -> new TreeSet<>()); + if (!names.isEmpty()) { + return names; + } try { QueryBuilder qb = cardsDao.queryBuilder(); qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName"); diff --git a/Mage/src/main/java/mage/constants/SubType.java b/Mage/src/main/java/mage/constants/SubType.java index 727dc6e793b..ec9aff18548 100644 --- a/Mage/src/main/java/mage/constants/SubType.java +++ b/Mage/src/main/java/mage/constants/SubType.java @@ -36,6 +36,7 @@ public enum SubType { MINE("Mine", SubTypeSet.NonBasicLandType), POWER_PLANT("Power-Plant", SubTypeSet.NonBasicLandType), TOWER("Tower", SubTypeSet.NonBasicLandType), + TOWN("Town", SubTypeSet.NonBasicLandType), // 205.3h Enchantments have their own unique set of subtypes; these subtypes are called enchantment types. AURA("Aura", SubTypeSet.EnchantmentType), BACKGROUND("Background", SubTypeSet.EnchantmentType), diff --git a/Mage/src/main/java/mage/counters/CounterType.java b/Mage/src/main/java/mage/counters/CounterType.java index 0afd7f62cb5..6cd4529ef60 100644 --- a/Mage/src/main/java/mage/counters/CounterType.java +++ b/Mage/src/main/java/mage/counters/CounterType.java @@ -38,6 +38,7 @@ public enum CounterType { BURDEN("burden"), CAGE("cage"), CARRION("carrion"), + CELL("cell"), CHARGE("charge"), CHIP("chip"), CHORUS("chorus"), @@ -219,6 +220,7 @@ public enum CounterType { STUN("stun"), SUPPLY("supply"), SUSPECT("suspect"), + TAKEOVER("takeover"), TASK("task"), THEFT("theft"), TIDE("tide"), diff --git a/Mage/src/main/java/mage/filter/StaticFilters.java b/Mage/src/main/java/mage/filter/StaticFilters.java index dfb7cc6b4c9..31d2277e82c 100644 --- a/Mage/src/main/java/mage/filter/StaticFilters.java +++ b/Mage/src/main/java/mage/filter/StaticFilters.java @@ -1,10 +1,7 @@ package mage.filter; import mage.ObjectColor; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.SuperType; -import mage.constants.TargetController; +import mage.constants.*; import mage.counters.CounterType; import mage.filter.common.*; import mage.filter.predicate.Predicates; @@ -61,6 +58,12 @@ public final class StaticFilters { FILTER_CARD_ENCHANTMENT.setLockedFilter(true); } + public static final FilterCard FILTER_CARD_ENCHANTMENTS = new FilterEnchantmentCard("enchantment cards"); + + static { + FILTER_CARD_ENCHANTMENTS.setLockedFilter(true); + } + public static final FilterArtifactCard FILTER_CARD_ARTIFACT = new FilterArtifactCard(); static { @@ -139,6 +142,12 @@ public final class StaticFilters { FILTER_CARD_ARTIFACT_FROM_YOUR_GRAVEYARD.setLockedFilter(true); } + public static final FilterCard FILTER_CARD_LAND_FROM_YOUR_GRAVEYARD = new FilterLandCard("land card from your graveyard"); + + static { + FILTER_CARD_LAND_FROM_YOUR_GRAVEYARD.setLockedFilter(true); + } + public static final FilterCreatureCard FILTER_CARD_CREATURE_A_GRAVEYARD = new FilterCreatureCard("creature card from a graveyard"); static { @@ -329,7 +338,7 @@ public final class StaticFilters { FILTER_PERMANENTS_ARTIFACT_CREATURE.setLockedFilter(true); } - public static final FilterControlledArtifactPermanent FILTER_ARTIFACT_NON_CREATURE = new FilterControlledArtifactPermanent("noncreature artifact"); + public static final FilterArtifactPermanent FILTER_ARTIFACT_NON_CREATURE = new FilterArtifactPermanent("noncreature artifact"); static { FILTER_ARTIFACT_NON_CREATURE.add(Predicates.not(CardType.CREATURE.getPredicate())); @@ -1033,6 +1042,21 @@ public final class StaticFilters { FILTER_SPELL_KICKED_A.setLockedFilter(true); } + public static final FilterSpell FILTER_SPELL_NO_MANA_SPENT = new FilterSpell("a spell, if no mana was spent to cast it"); + + static { + FILTER_SPELL_NO_MANA_SPENT.add(new ManaSpentToCastPredicate(ComparisonType.EQUAL_TO, 0)); + FILTER_SPELL_NO_MANA_SPENT.setLockedFilter(true); + } + + public static final FilterSpell FILTER_NONCREATURE_SPELL_FOUR_MANA_SPENT = new FilterSpell("a noncreature spell, if at least four mana was spent to cast it"); + + static { + FILTER_NONCREATURE_SPELL_FOUR_MANA_SPENT.add(Predicates.not(CardType.CREATURE.getPredicate())); + FILTER_NONCREATURE_SPELL_FOUR_MANA_SPENT.add(new ManaSpentToCastPredicate(ComparisonType.MORE_THAN, 3)); + FILTER_NONCREATURE_SPELL_FOUR_MANA_SPENT.setLockedFilter(true); + } + public static final FilterPermanent FILTER_PERMANENT_TOKEN = new FilterPermanent("token"); static { diff --git a/Mage/src/main/java/mage/filter/predicate/mageobject/ManaSpentToCastPredicate.java b/Mage/src/main/java/mage/filter/predicate/mageobject/ManaSpentToCastPredicate.java new file mode 100644 index 00000000000..c9f44076895 --- /dev/null +++ b/Mage/src/main/java/mage/filter/predicate/mageobject/ManaSpentToCastPredicate.java @@ -0,0 +1,25 @@ +package mage.filter.predicate.mageobject; + +import mage.constants.ComparisonType; +import mage.filter.predicate.IntComparePredicate; +import mage.game.stack.StackObject; + +/** + * @author TheElk801 + */ +public class ManaSpentToCastPredicate extends IntComparePredicate { + + public ManaSpentToCastPredicate(ComparisonType type, int value) { + super(type, value); + } + + @Override + protected int getInputValue(StackObject input) { + return input.getStackAbility().getManaCostsToPay().getUsedManaToPay().count(); + } + + @Override + public String toString() { + return "ManaSpent" + super.toString(); + } +} diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index 7ff0e1610a7..fc4269a71e9 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -1522,7 +1522,7 @@ public abstract class GameImpl implements Game { UUID[] players = getPlayers().keySet().toArray(new UUID[0]); UUID playerId; while (!hasEnded()) { - playerId = players[RandomUtil.nextInt(players.length)]; + playerId = players[RandomUtil.nextInt(players.length)]; // test game Player player = getPlayer(playerId); if (player != null && player.canRespond()) { fireInformEvent(state.getPlayer(playerId).getLogName() + " won the toss"); @@ -1810,14 +1810,21 @@ public abstract class GameImpl implements Game { protected void resolve() { StackObject top = null; + boolean wasError = false; try { top = state.getStack().peek(); top.resolve(this); resetControlAfterSpellResolve(top.getId()); + } catch (Throwable e) { + // workaround to show real error in tests instead checkInfiniteLoop + wasError = true; + throw e; } finally { if (top != null) { state.getStack().remove(top, this); // seems partly redundant because move card from stack to grave is already done and the stack removed - checkInfiniteLoop(top.getSourceId()); + if (!wasError) { + checkInfiniteLoop(top.getSourceId()); + } if (!getTurn().isEndTurnRequested()) { while (state.hasSimultaneousEvents()) { state.handleSimultaneousEvent(this); @@ -3746,7 +3753,7 @@ public abstract class GameImpl implements Game { @Override public void cheat(UUID ownerId, List library, List hand, List battlefield, List graveyard, List command, List exiled) { // fake test ability for triggers and events - Ability fakeSourceAbilityTemplate = new SimpleStaticAbility(Zone.OUTSIDE, new InfoEffect("adding testing cards")); + Ability fakeSourceAbilityTemplate = new SimpleStaticAbility(Zone.OUTSIDE, new InfoEffect("fake ability")); fakeSourceAbilityTemplate.setControllerId(ownerId); Player player = getPlayer(ownerId); @@ -4014,7 +4021,6 @@ public abstract class GameImpl implements Game { playerObject.resetStoredBookmark(this); playerObject.resetPlayerPassedActions(); playerObject.abort(); - } } fireUpdatePlayersEvent(); diff --git a/Mage/src/main/java/mage/game/GameState.java b/Mage/src/main/java/mage/game/GameState.java index 3f13573471d..c9963203f9e 100644 --- a/Mage/src/main/java/mage/game/GameState.java +++ b/Mage/src/main/java/mage/game/GameState.java @@ -724,7 +724,7 @@ public class GameState implements Serializable, Copyable { /** * Returns a list of all players of the game ignoring range or if a player * has lost or left the game. - * + *

* Warning, it's ignore range, must be used by game engine only. */ public PlayerList getPlayerList() { @@ -734,7 +734,7 @@ public class GameState implements Serializable, Copyable { /** * Returns a list of all active players of the game, setting the playerId to * the current player of the list. - * + *

* Warning, it's ignore range, must be used by game engine only. */ public PlayerList getPlayerList(UUID playerId) { @@ -1369,8 +1369,9 @@ public class GameState implements Serializable, Copyable { * @param valueId * @param value */ - public void setValue(String valueId, Object value) { + public T setValue(String valueId, T value) { values.put(valueId, value); + return value; } /** diff --git a/Mage/src/main/java/mage/game/ZonesHandler.java b/Mage/src/main/java/mage/game/ZonesHandler.java index c4f428eeb57..65d336cdf1a 100644 --- a/Mage/src/main/java/mage/game/ZonesHandler.java +++ b/Mage/src/main/java/mage/game/ZonesHandler.java @@ -458,10 +458,14 @@ public final class ZonesHandler { target.setRequired(true); while (player.canRespond() && cards.size() > 1) { player.choose(Outcome.Neutral, cards, target, source, game); - UUID targetObjectId = target.getFirstTarget(); - order.add(cards.get(targetObjectId, game)); - cards.remove(targetObjectId); - target.clearChosen(); + Card card = cards.get(target.getFirstTarget(), game); + if (card != null) { + order.add(card); + cards.remove(target.getFirstTarget()); + target.clearChosen(); + } else { + break; + } } order.addAll(cards.getCards(game)); return order; diff --git a/Mage/src/main/java/mage/game/combat/CombatGroup.java b/Mage/src/main/java/mage/game/combat/CombatGroup.java index 03a492dd05f..9940f345875 100644 --- a/Mage/src/main/java/mage/game/combat/CombatGroup.java +++ b/Mage/src/main/java/mage/game/combat/CombatGroup.java @@ -382,7 +382,7 @@ public class CombatGroup implements Serializable, Copyable { break; } int damageAssigned = 0; - damageAssigned = player.getAmount(0, damage, "Assign damage to " + defendingCreature.getName(), game); + damageAssigned = player.getAmount(0, damage, "Assign damage to " + defendingCreature.getName(), null, game); assigned.put(defendingCreature.getId(), damageAssigned); damage -= damageAssigned; } @@ -755,9 +755,9 @@ public class CombatGroup implements Serializable, Copyable { */ private int getDamageValueFromPermanent(Permanent permanent, Game game) { if (game.getCombat().useToughnessForDamage(permanent, game)) { - return permanent.getToughness().getValue(); + return Math.max(0, permanent.getToughness().getValue()); } else { - return permanent.getPower().getValue(); + return Math.max(0, permanent.getPower().getValue()); } } diff --git a/Mage/src/main/java/mage/game/command/emblems/JayaFieryNegotiatorEmblem.java b/Mage/src/main/java/mage/game/command/emblems/JayaFieryNegotiatorEmblem.java index 06b7a64b7a7..61f1d927c26 100644 --- a/Mage/src/main/java/mage/game/command/emblems/JayaFieryNegotiatorEmblem.java +++ b/Mage/src/main/java/mage/game/command/emblems/JayaFieryNegotiatorEmblem.java @@ -5,6 +5,8 @@ import mage.abilities.Ability; import mage.abilities.common.SpellCastControllerTriggeredAbility; import mage.abilities.effects.OneShotEffect; import mage.constants.Outcome; +import mage.constants.SetTargetPointer; +import mage.constants.Zone; import mage.filter.FilterSpell; import mage.filter.common.FilterInstantOrSorcerySpell; import mage.filter.predicate.mageobject.ColorPredicate; @@ -26,8 +28,8 @@ public final class JayaFieryNegotiatorEmblem extends Emblem { // −8: You get an emblem with "Whenever you cast a red instant or sorcery spell, copy it twice. You may choose new targets for the copies." public JayaFieryNegotiatorEmblem() { super("Emblem Jaya"); - this.getAbilities().add(new SpellCastControllerTriggeredAbility( - new JayaFieryNegotiatorEmblemEffect(), filter, false + this.getAbilities().add(new SpellCastControllerTriggeredAbility(Zone.COMMAND, + new JayaFieryNegotiatorEmblemEffect(), filter, false, SetTargetPointer.NONE )); } diff --git a/Mage/src/main/java/mage/game/command/emblems/SephirothOneWingedAngelEmblem.java b/Mage/src/main/java/mage/game/command/emblems/SephirothOneWingedAngelEmblem.java new file mode 100644 index 00000000000..3d30bdf66ce --- /dev/null +++ b/Mage/src/main/java/mage/game/command/emblems/SephirothOneWingedAngelEmblem.java @@ -0,0 +1,37 @@ +package mage.game.command.emblems; + +import mage.abilities.Ability; +import mage.abilities.common.DiesCreatureTriggeredAbility; +import mage.abilities.effects.common.LoseLifeSourceControllerEffect; +import mage.abilities.effects.common.LoseLifeTargetEffect; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.game.command.Emblem; +import mage.target.common.TargetOpponent; + +/** + * @author TheElk801 + */ +public final class SephirothOneWingedAngelEmblem extends Emblem { + + // you get an emblem with "Whenever a creature dies, target opponent loses 1 life and you gain 1 life." + public SephirothOneWingedAngelEmblem() { + super("Emblem Sephiroth"); + Ability ability = new DiesCreatureTriggeredAbility( + Zone.COMMAND, new LoseLifeTargetEffect(1), false, + StaticFilters.FILTER_PERMANENT_A_CREATURE, false + ); + ability.addEffect(new LoseLifeSourceControllerEffect(1).concatBy("and")); + ability.addTarget(new TargetOpponent()); + this.getAbilities().add(ability); + } + + private SephirothOneWingedAngelEmblem(final SephirothOneWingedAngelEmblem card) { + super(card); + } + + @Override + public SephirothOneWingedAngelEmblem copy() { + return new SephirothOneWingedAngelEmblem(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/AlienAngelToken.java b/Mage/src/main/java/mage/game/permanent/token/AlienAngelToken.java new file mode 100644 index 00000000000..c871d9e464a --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/AlienAngelToken.java @@ -0,0 +1,72 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SpellCastOpponentTriggeredAbility; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; + +/** + * @author TheElk801 + */ +public final class AlienAngelToken extends TokenImpl { + + public AlienAngelToken() { + super("Alien Angel Token", "2/2 black Alien Angel artifact creature token with first strike, vigilance, and \"Whenever an opponent casts a creature spell, this token isn't a creature until end of turn.\""); + cardType.add(CardType.ARTIFACT); + cardType.add(CardType.CREATURE); + color.setBlack(true); + subtype.add(SubType.ALIEN); + subtype.add(SubType.ANGEL); + power = new MageInt(2); + toughness = new MageInt(2); + + addAbility(FirstStrikeAbility.getInstance()); + addAbility(VigilanceAbility.getInstance()); + addAbility(new SpellCastOpponentTriggeredAbility( + new AlienAngelTokenEffect(), StaticFilters.FILTER_SPELL_A_CREATURE, false + )); + } + + private AlienAngelToken(final AlienAngelToken token) { + super(token); + } + + public AlienAngelToken copy() { + return new AlienAngelToken(this); + } +} + +class AlienAngelTokenEffect extends ContinuousEffectImpl { + + AlienAngelTokenEffect() { + super(Duration.EndOfTurn, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.UnboostCreature); + staticText = "this token isn't a creature until end of turn"; + } + + private AlienAngelTokenEffect(final AlienAngelTokenEffect effect) { + super(effect); + } + + @Override + public AlienAngelTokenEffect copy() { + return new AlienAngelTokenEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + if (permanent == null) { + discard(); + return false; + } + permanent.removeAllSubTypes(game, SubTypeSet.CreatureType); + permanent.removeCardType(game, CardType.CREATURE); + return true; + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/AngeloToken.java b/Mage/src/main/java/mage/game/permanent/token/AngeloToken.java new file mode 100644 index 00000000000..7e958450adc --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/AngeloToken.java @@ -0,0 +1,32 @@ +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 AngeloToken extends TokenImpl { + + public AngeloToken() { + super("Angelo", "Angelo, a legendary 1/1 green and white Dog creature token"); + supertype.add(SuperType.LEGENDARY); + cardType.add(CardType.CREATURE); + subtype.add(SubType.DOG); + + color.setGreen(true); + color.setWhite(true); + power = new MageInt(1); + toughness = new MageInt(1); + } + + private AngeloToken(final AngeloToken token) { + super(token); + } + + public AngeloToken copy() { + return new AngeloToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/BirdVigilanceToken.java b/Mage/src/main/java/mage/game/permanent/token/BirdVigilanceToken.java new file mode 100644 index 00000000000..7ee2477cd96 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/BirdVigilanceToken.java @@ -0,0 +1,34 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.constants.CardType; +import mage.constants.SubType; + +/** + * @author TheElk801 + */ +public final class BirdVigilanceToken extends TokenImpl { + + public BirdVigilanceToken() { + super("Bird Token", "1/1 blue Bird creature token with flying and vigilance"); + cardType.add(CardType.CREATURE); + color.setBlue(true); + subtype.add(SubType.BIRD); + power = new MageInt(1); + toughness = new MageInt(1); + + addAbility(FlyingAbility.getInstance()); + addAbility(VigilanceAbility.getInstance()); + } + + private BirdVigilanceToken(final BirdVigilanceToken token) { + super(token); + } + + @Override + public BirdVigilanceToken copy() { + return new BirdVigilanceToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/BlackWizardToken.java b/Mage/src/main/java/mage/game/permanent/token/BlackWizardToken.java new file mode 100644 index 00000000000..d2c8a8541bc --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/BlackWizardToken.java @@ -0,0 +1,37 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.common.DamagePlayersEffect; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.TargetController; +import mage.filter.StaticFilters; + +/** + * @author TheElk801 + */ +public final class BlackWizardToken extends TokenImpl { + + public BlackWizardToken() { + super("Wizard Token", "0/1 black Wizard creature token with \"Whenever you cast a noncreature spell, this token deals 1 damage to each opponent.\""); + cardType.add(CardType.CREATURE); + color.setBlack(true); + subtype.add(SubType.WIZARD); + power = new MageInt(0); + toughness = new MageInt(1); + + addAbility(new SpellCastControllerTriggeredAbility( + new DamagePlayersEffect(1, TargetController.OPPONENT), + StaticFilters.FILTER_SPELL_A_NON_CREATURE, false + )); + } + + private BlackWizardToken(final BlackWizardToken token) { + super(token); + } + + public BlackWizardToken copy() { + return new BlackWizardToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/CursedRoleToken.java b/Mage/src/main/java/mage/game/permanent/token/CursedRoleToken.java index 37acde4c8a7..6f146adde18 100644 --- a/Mage/src/main/java/mage/game/permanent/token/CursedRoleToken.java +++ b/Mage/src/main/java/mage/game/permanent/token/CursedRoleToken.java @@ -3,8 +3,9 @@ package mage.game.permanent.token; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.AttachEffect; -import mage.abilities.effects.common.continuous.SetBasePowerToughnessEnchantedEffect; +import mage.abilities.effects.common.continuous.SetBasePowerToughnessAttachedEffect; import mage.abilities.keyword.EnchantAbility; +import mage.constants.AttachmentType; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; @@ -29,7 +30,7 @@ public final class CursedRoleToken extends TokenImpl { this.addAbility(ability); // Enchanted creature is 1/1. - this.addAbility(new SimpleStaticAbility(new SetBasePowerToughnessEnchantedEffect(1, 1))); + this.addAbility(new SimpleStaticAbility(new SetBasePowerToughnessAttachedEffect(1, 1, AttachmentType.AURA))); } private CursedRoleToken(final CursedRoleToken token) { diff --git a/Mage/src/main/java/mage/game/permanent/token/DragonEggDragonToken.java b/Mage/src/main/java/mage/game/permanent/token/DragonEggDragonToken.java index b82605fc24b..3c8faf936c5 100644 --- a/Mage/src/main/java/mage/game/permanent/token/DragonEggDragonToken.java +++ b/Mage/src/main/java/mage/game/permanent/token/DragonEggDragonToken.java @@ -8,7 +8,6 @@ import mage.abilities.keyword.FlyingAbility; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; -import mage.constants.Zone; /** * @author spjspj @@ -16,7 +15,7 @@ import mage.constants.Zone; public final class DragonEggDragonToken extends TokenImpl { public DragonEggDragonToken() { - super("Dragon Token", "2/2 red Dragon creature token with flying and \"{R}: This creature gets +1/+0 until end of turn.\""); + super("Dragon Token", "2/2 red Dragon creature token with flying and \"{R}: This token gets +1/+0 until end of turn.\""); cardType.add(CardType.CREATURE); color.setRed(true); subtype.add(SubType.DRAGON); @@ -24,8 +23,7 @@ public final class DragonEggDragonToken extends TokenImpl { toughness = new MageInt(2); this.addAbility(FlyingAbility.getInstance()); - this.addAbility(new SimpleActivatedAbility(new BoostSourceEffect(1, 0, Duration.EndOfTurn) - .setText("This creature gets +1/+0 until end of turn"), new ManaCostsImpl<>("{R}"))); + this.addAbility(new SimpleActivatedAbility(new BoostSourceEffect(1, 0, Duration.EndOfTurn), new ManaCostsImpl<>("{R}"))); } private DragonEggDragonToken(final DragonEggDragonToken token) { diff --git a/Mage/src/main/java/mage/game/permanent/token/FoodToken.java b/Mage/src/main/java/mage/game/permanent/token/FoodToken.java index 8e79edfae4a..18eb4b0ac34 100644 --- a/Mage/src/main/java/mage/game/permanent/token/FoodToken.java +++ b/Mage/src/main/java/mage/game/permanent/token/FoodToken.java @@ -15,7 +15,7 @@ public final class FoodToken extends TokenImpl { cardType.add(CardType.ARTIFACT); subtype.add(SubType.FOOD); - this.addAbility(new FoodAbility(false)); + this.addAbility(new FoodAbility()); } private FoodToken(final FoodToken token) { diff --git a/Mage/src/main/java/mage/game/permanent/token/HeroToken.java b/Mage/src/main/java/mage/game/permanent/token/HeroToken.java new file mode 100644 index 00000000000..2169f1ac480 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/HeroToken.java @@ -0,0 +1,23 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.constants.CardType; +import mage.constants.SubType; + +/** + * @author balazskristof + */ +public class HeroToken extends TokenImpl { + + public HeroToken() { + super("Hero Token", "1/1 colorless Hero creature token"); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + cardType.add(CardType.CREATURE); + subtype.add(SubType.HERO); + } + + private HeroToken(final HeroToken token) { super(token); } + + public HeroToken copy() { return new HeroToken(this); } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/MoogleToken.java b/Mage/src/main/java/mage/game/permanent/token/MoogleToken.java new file mode 100644 index 00000000000..eb997ca63d2 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/MoogleToken.java @@ -0,0 +1,32 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.abilities.keyword.LifelinkAbility; +import mage.constants.CardType; +import mage.constants.SubType; + +/** + * @author TheElk801 + */ +public final class MoogleToken extends TokenImpl { + + public MoogleToken() { + super("Moogle Token", "1/2 white Moogle creature token with lifelink"); + cardType.add(CardType.CREATURE); + color.setWhite(true); + subtype.add(SubType.MOOGLE); + power = new MageInt(1); + toughness = new MageInt(2); + + addAbility(LifelinkAbility.getInstance()); + } + + private MoogleToken(final MoogleToken token) { + super(token); + } + + @Override + public MoogleToken copy() { + return new MoogleToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/NestingDragonToken.java b/Mage/src/main/java/mage/game/permanent/token/NestingDragonToken.java index a8702a844da..a9f93c20146 100644 --- a/Mage/src/main/java/mage/game/permanent/token/NestingDragonToken.java +++ b/Mage/src/main/java/mage/game/permanent/token/NestingDragonToken.java @@ -17,9 +17,9 @@ public final class NestingDragonToken extends TokenImpl { "Dragon Egg Token", "0/2 red Dragon Egg creature token with defender and " + "\"" - + "When this creature dies, " + + "When this token dies, " + "create a 2/2 red Dragon creature token with flying and " - + "'{R}: This creature gets +1/+0 until end of turn.'" + + "'{R}: This token gets +1/+0 until end of turn.'" + "\"" ); cardType.add(CardType.CREATURE); diff --git a/Mage/src/main/java/mage/game/permanent/token/RatCantBlockToken.java b/Mage/src/main/java/mage/game/permanent/token/RatCantBlockToken.java index 7d9d9f3a011..5bac0547847 100644 --- a/Mage/src/main/java/mage/game/permanent/token/RatCantBlockToken.java +++ b/Mage/src/main/java/mage/game/permanent/token/RatCantBlockToken.java @@ -13,7 +13,7 @@ import mage.constants.SubType; public final class RatCantBlockToken extends TokenImpl { public RatCantBlockToken() { - super("Rat Token", "1/1 black Rat creature token with \"This creature can't block.\""); + super("Rat Token", "1/1 black Rat creature token with \"This token can't block.\""); cardType.add(CardType.CREATURE); color.setBlack(true); subtype.add(SubType.RAT); @@ -22,7 +22,7 @@ public final class RatCantBlockToken extends TokenImpl { this.addAbility(new SimpleStaticAbility( new CantBlockSourceEffect(Duration.WhileOnBattlefield) - .setText("this creature can't block") + .setText("this token can't block") )); } diff --git a/Mage/src/main/java/mage/game/permanent/token/RobotBlueToken.java b/Mage/src/main/java/mage/game/permanent/token/RobotBlueToken.java new file mode 100644 index 00000000000..c3a2e7996e1 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/RobotBlueToken.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 RobotBlueToken extends TokenImpl { + + public RobotBlueToken() { + super("Robot Token", "3/3 blue Robot Warrior artifact creature token"); + cardType.add(CardType.ARTIFACT); + cardType.add(CardType.CREATURE); + color.setBlue(true); + subtype.add(SubType.ROBOT); + power = new MageInt(3); + toughness = new MageInt(3); + } + + private RobotBlueToken(final RobotBlueToken token) { + super(token); + } + + public RobotBlueToken copy() { + return new RobotBlueToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/SettlementToken.java b/Mage/src/main/java/mage/game/permanent/token/SettlementToken.java new file mode 100644 index 00000000000..7cfbc44140f --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/SettlementToken.java @@ -0,0 +1,44 @@ +package mage.game.permanent.token; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.abilities.mana.AnyColorManaAbility; +import mage.constants.*; +import mage.target.TargetPermanent; +import mage.target.common.TargetLandPermanent; + +/** + * @author TheElk801 + */ +public final class SettlementToken extends TokenImpl { + + public SettlementToken() { + super("Settlement", "Settlement token"); + cardType.add(CardType.ENCHANTMENT); + color.setGreen(true); + subtype.add(SubType.AURA); + + TargetPermanent auraTarget = new TargetLandPermanent(); + Ability ability = new EnchantAbility(auraTarget); + ability.addTarget(auraTarget); + ability.addEffect(new AttachEffect(Outcome.Benefit)); + this.addAbility(ability); + + // Enchanted land has "{T}: Add one mana of any color." + this.addAbility(new SimpleStaticAbility(new GainAbilityAttachedEffect( + new AnyColorManaAbility(), AttachmentType.AURA, Duration.WhileOnBattlefield, + "enchanted land has \"{T}: Add one mana of any color.\"" + ))); + } + + private SettlementToken(final SettlementToken token) { + super(token); + } + + public SettlementToken copy() { + return new SettlementToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/TheBlackjackToken.java b/Mage/src/main/java/mage/game/permanent/token/TheBlackjackToken.java new file mode 100644 index 00000000000..cd0c2212c73 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/TheBlackjackToken.java @@ -0,0 +1,34 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.abilities.keyword.CrewAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; + +/** + * @author TheElk801 + */ +public final class TheBlackjackToken extends TokenImpl { + + public TheBlackjackToken() { + super("The Blackjack", "The Blackjack, a legendary 3/3 colorless Vehicle artifact token with flying and crew 2"); + supertype.add(SuperType.LEGENDARY); + cardType.add(CardType.ARTIFACT); + subtype.add(SubType.VEHICLE); + power = new MageInt(3); + toughness = new MageInt(3); + + this.addAbility(FlyingAbility.getInstance()); + this.addAbility(new CrewAbility(2)); + } + + private TheBlackjackToken(final TheBlackjackToken token) { + super(token); + } + + public TheBlackjackToken copy() { + return new TheBlackjackToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/TheGirlInTheFireplaceHumanNobleToken.java b/Mage/src/main/java/mage/game/permanent/token/TheGirlInTheFireplaceHumanNobleToken.java index 7e19bb8edc5..55452a5e1d8 100644 --- a/Mage/src/main/java/mage/game/permanent/token/TheGirlInTheFireplaceHumanNobleToken.java +++ b/Mage/src/main/java/mage/game/permanent/token/TheGirlInTheFireplaceHumanNobleToken.java @@ -14,7 +14,7 @@ import mage.constants.Duration; public final class TheGirlInTheFireplaceHumanNobleToken extends TokenImpl { public TheGirlInTheFireplaceHumanNobleToken() { - super("Human Noble Token", "1/1 white Human Noble creature token with vanishing 3 and \"Prevent all damage that would be dealt to this creature.\""); + super("Human Noble Token", "1/1 white Human Noble creature token with vanishing 3 and \"Prevent all damage that would be dealt to this token.\""); cardType.add(CardType.CREATURE); color.setWhite(true); subtype.add(SubType.HUMAN,SubType.NOBLE); @@ -25,7 +25,7 @@ public final class TheGirlInTheFireplaceHumanNobleToken extends TokenImpl { new PreventDamageToSourceEffect( Duration.WhileOnBattlefield, Integer.MAX_VALUE - ).setText("Prevent all damage that would be dealt to this creature.") + ).setText("Prevent all damage that would be dealt to this token.") )); } diff --git a/Mage/src/main/java/mage/game/permanent/token/TokenImpl.java b/Mage/src/main/java/mage/game/permanent/token/TokenImpl.java index 57af383b9ba..db1d66668d7 100644 --- a/Mage/src/main/java/mage/game/permanent/token/TokenImpl.java +++ b/Mage/src/main/java/mage/game/permanent/token/TokenImpl.java @@ -5,6 +5,7 @@ import mage.abilities.Ability; import mage.abilities.SpellAbility; import mage.abilities.effects.Effect; import mage.abilities.effects.common.AttachEffect; +import mage.abilities.keyword.BestowAbility; import mage.abilities.keyword.EnchantAbility; import mage.cards.Card; import mage.cards.repository.TokenInfo; @@ -438,7 +439,9 @@ public abstract class TokenImpl extends MageObjectImpl implements Token { // end of messy target-groping code to handle auras // this section is for tokens created attached to a specific known object - if (permanentAttachedTo != null) { + boolean isBestow = permanent.getAbilities().stream() + .anyMatch(ability -> ability instanceof BestowAbility); + if (permanentAttachedTo != null && !isBestow) { if (permanent.hasSubtype(SubType.AURA, game)) { permanent.getAbilities().get(0).getTargets().get(0).add(permanentAttachedTo.getId(), game); permanent.getAbilities().get(0).getEffects().get(0).apply(game, permanent.getAbilities().get(0)); diff --git a/Mage/src/main/java/mage/players/Library.java b/Mage/src/main/java/mage/players/Library.java index fcc84fca2bb..c4fa44134fa 100644 --- a/Mage/src/main/java/mage/players/Library.java +++ b/Mage/src/main/java/mage/players/Library.java @@ -168,6 +168,8 @@ public class Library implements Serializable { } public Collection getUniqueCards(Game game) { + // TODO: on no performance issues - remove unique code after few releases, 2025-05-13 + if (true) return getCards(game); Map cards = new HashMap<>(); for (UUID cardId : library) { Card card = game.getCard(cardId); diff --git a/Mage/src/main/java/mage/players/Player.java b/Mage/src/main/java/mage/players/Player.java index 987b47e37f8..a0bc38d90ff 100644 --- a/Mage/src/main/java/mage/players/Player.java +++ b/Mage/src/main/java/mage/players/Player.java @@ -5,7 +5,6 @@ import mage.abilities.*; import mage.abilities.costs.AlternativeSourceCosts; import mage.abilities.costs.Cost; import mage.abilities.costs.Costs; -import mage.abilities.costs.VariableCost; import mage.abilities.costs.mana.ManaCost; import mage.abilities.costs.mana.ManaCosts; import mage.abilities.mana.ManaOptions; @@ -672,6 +671,10 @@ public interface Player extends MageItem, Copyable { boolean priority(Game game); + /** + * Warning, any choose and chooseTarget dialogs must return false to stop choosing, e.g. no more possible targets + * Same logic as "something changes" in "apply" + */ boolean choose(Outcome outcome, Target target, Ability source, Game game); boolean choose(Outcome outcome, Target target, Ability source, Game game, Map options); @@ -744,16 +747,12 @@ public interface Player extends MageItem, Copyable { boolean shuffleCardsToLibrary(Card card, Game game, Ability source); /** - * Set the value for X mana spells and abilities + * Set the value for X in spells and abilities + * @param isManaPay helper param for better AI logic */ - int announceXMana(int min, int max, String message, Game game, Ability ability); + int announceX(int min, int max, String message, Game game, Ability source, boolean isManaPay); - /** - * Set the value for non mana X costs - */ - int announceXCost(int min, int max, String message, Game game, Ability ability, VariableCost variableCost); - - // TODO: rework to use pair's list of effect + ability instead string's map + // TODO: rework to use pair's list of effect + ability instead string's map int chooseReplacementEffect(Map effectsMap, Map objectsMap, Game game); TriggeredAbility chooseTriggeredAbility(List abilities, Game game); @@ -764,7 +763,11 @@ public interface Player extends MageItem, Copyable { void selectBlockers(Ability source, Game game, UUID defendingPlayerId); - int getAmount(int min, int max, String message, Game game); + /** + * + * @param source can be null for system actions like define damage + */ + int getAmount(int min, int max, String message, Ability source, Game game); /** * Player distributes amount among multiple options diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 1ec2badc1b7..02f2f2db436 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -886,7 +886,7 @@ public abstract class PlayerImpl implements Player, Serializable { return toDiscard; } TargetDiscard target = new TargetDiscard(minAmount, maxAmount, StaticFilters.FILTER_CARD, getId()); - choose(Outcome.Discard, target, source, game); + target.choose(Outcome.Discard, getId(), source, game); toDiscard.addAll(target.getTargets()); return toDiscard; } @@ -2932,7 +2932,7 @@ public abstract class PlayerImpl implements Player, Serializable { count = Math.min(searchingLibrary.count(target.getFilter(), game), librarySearchLimit); } - if (count < target.getNumberOfTargets()) { + if (count < target.getMinNumberOfTargets()) { newTarget.setMinNumberOfTargets(count); } @@ -4472,14 +4472,14 @@ public abstract class PlayerImpl implements Player, Serializable { List options = new ArrayList<>(); if (ability.isModal()) { addModeOptions(options, ability, game); - } else if (!ability.getTargets().getUnchosen(game).isEmpty()) { + } else if (ability.getTargets().getNextUnchosen(game) != null) { // TODO: Handle other variable costs than mana costs if (!ability.getManaCosts().getVariableCosts().isEmpty()) { addVariableXOptions(options, ability, 0, game); } else { addTargetOptions(options, ability, 0, game); } - } else if (!ability.getCosts().getTargets().getUnchosen(game).isEmpty()) { + } else if (ability.getCosts().getTargets().getNextUnchosen(game) != null) { addCostTargetOptions(options, ability, 0, game); } @@ -4497,13 +4497,13 @@ public abstract class PlayerImpl implements Player, Serializable { newOption.getModes().clearSelectedModes(); newOption.getModes().addSelectedMode(mode.getId()); newOption.getModes().setActiveMode(mode); - if (!newOption.getTargets().getUnchosen(game).isEmpty()) { + if (newOption.getTargets().getNextUnchosen(game) != null) { if (!newOption.getManaCosts().getVariableCosts().isEmpty()) { addVariableXOptions(options, newOption, 0, game); } else { addTargetOptions(options, newOption, 0, game); } - } else if (!newOption.getCosts().getTargets().getUnchosen(game).isEmpty()) { + } else if (newOption.getCosts().getTargets().getNextUnchosen(game) != null) { addCostTargetOptions(options, newOption, 0, game); } else { options.add(newOption); @@ -4523,7 +4523,9 @@ public abstract class PlayerImpl implements Player, Serializable { */ protected void addTargetOptions(List options, Ability option, int targetNum, Game game) { // TODO: target options calculated for triggered ability too, but do not used in real game - for (Target target : option.getTargets().getUnchosen(game).get(targetNum).getTargetOptions(option, game)) { + // TODO: there are rare errors with wrong targetNum - maybe multiple game sims can change same target object somehow? + // do not hide NullPointError here, research instead + for (Target target : option.getTargets().getNextUnchosen(game, targetNum).getTargetOptions(option, game)) { Ability newOption = option.copy(); if (target instanceof TargetAmount) { for (UUID targetId : target.getTargets()) { @@ -5562,8 +5564,7 @@ public abstract class PlayerImpl implements Player, Serializable { TargetPermanent target = new TargetControlledCreaturePermanent(); target.withNotTarget(true); target.withChooseHint("to be your Ring-bearer"); - choose(Outcome.Neutral, target, null, game); - + target.choose(Outcome.Neutral, getId(), null, null, game); newBearerId = target.getFirstTarget(); } else { newBearerId = currentBearerId; diff --git a/Mage/src/main/java/mage/players/StubPlayer.java b/Mage/src/main/java/mage/players/StubPlayer.java index da4d4167d2d..d39f65a1444 100644 --- a/Mage/src/main/java/mage/players/StubPlayer.java +++ b/Mage/src/main/java/mage/players/StubPlayer.java @@ -42,7 +42,9 @@ public class StubPlayer extends PlayerImpl { public boolean choose(Outcome outcome, Target target, Ability source, Game game) { if (target instanceof TargetPlayer) { for (Player player : game.getPlayers().values()) { - if (player.getId().equals(getId()) && target.canTarget(getId(), game)) { + if (player.getId().equals(getId()) + && target.canTarget(getId(), game) + && !target.contains(getId())) { target.add(player.getId(), game); return true; } @@ -154,13 +156,8 @@ public class StubPlayer extends PlayerImpl { } @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; + public int announceX(int min, int max, String message, Game game, Ability source, boolean isManaPay) { + return min; } @Override @@ -189,8 +186,8 @@ public class StubPlayer extends PlayerImpl { } @Override - public int getAmount(int min, int max, String message, Game game) { - return 0; + public int getAmount(int min, int max, String message, Ability source, Game game) { + return min; } @Override diff --git a/Mage/src/main/java/mage/target/Target.java b/Mage/src/main/java/mage/target/Target.java index 92fd3dbea7c..e5268c1ea98 100644 --- a/Mage/src/main/java/mage/target/Target.java +++ b/Mage/src/main/java/mage/target/Target.java @@ -7,6 +7,7 @@ import mage.constants.Zone; import mage.filter.Filter; import mage.game.Game; import mage.players.Player; +import mage.util.Copyable; import java.io.Serializable; import java.util.Collection; @@ -17,14 +18,24 @@ import java.util.UUID; /** * @author BetaSteward_at_googlemail.com */ -public interface Target extends Serializable { +public interface Target extends Copyable, Serializable { + /** + * All targets selected by a player + *

+ * Warning, for "up to" targets it will return true all the time, so make sure your dialog + * use do-while logic and call "choose" one time min or use isChoiceCompleted + */ boolean isChosen(Game game); - boolean doneChoosing(Game game); + boolean isChoiceCompleted(Game game); + + boolean isChoiceCompleted(UUID abilityControllerId, Ability source, Game game); void clearChosen(); + boolean isChoiceSelected(); + boolean isNotTarget(); /** @@ -53,6 +64,9 @@ public interface Target extends Serializable { */ Set possibleTargets(UUID sourceControllerId, Ability source, Game game); + /** + * Priority method to make a choice from cards and other places, not a player.chooseXXX + */ boolean chooseTarget(Outcome outcome, UUID playerId, Ability source, Game game); /** @@ -91,10 +105,19 @@ public interface Target extends Serializable { Set possibleTargets(UUID sourceControllerId, Game game); + @Deprecated // TODO: need replace to source only version? boolean choose(Outcome outcome, UUID playerId, UUID sourceId, Ability source, Game game); + /** + * Priority method to make a choice from cards and other places, not a player.chooseXXX + */ + default boolean choose(Outcome outcome, UUID playerId, Ability source, Game game) { + return choose(outcome, playerId, source == null ? null : source.getSourceId(), source, game); + } + /** * Add target from non targeting methods like choose + * TODO: need usage research, looks like there are wrong usage of addTarget, e.g. in choose method (must be add) */ void add(UUID id, Game game); @@ -129,8 +152,6 @@ public interface Target extends Serializable { int getTargetAmount(UUID targetId); - int getNumberOfTargets(); - int getMinNumberOfTargets(); int getMaxNumberOfTargets(); @@ -163,6 +184,7 @@ public interface Target extends Serializable { UUID getFirstTarget(); + @Override Target copy(); // some targets are chosen from players that are not the controller of the ability (e.g. Pandemonium) diff --git a/Mage/src/main/java/mage/target/TargetAmount.java b/Mage/src/main/java/mage/target/TargetAmount.java index d128d38ec9b..ccbfde6d66a 100644 --- a/Mage/src/main/java/mage/target/TargetAmount.java +++ b/Mage/src/main/java/mage/target/TargetAmount.java @@ -46,11 +46,11 @@ public abstract class TargetAmount extends TargetImpl { @Override public boolean isChosen(Game game) { - return doneChoosing(game); + return isChoiceCompleted(game); } @Override - public boolean doneChoosing(Game game) { + public boolean isChoiceCompleted(Game game) { return amountWasSet && (remainingAmount == 0 || (getMinNumberOfTargets() < getMaxNumberOfTargets() @@ -109,17 +109,21 @@ public abstract class TargetAmount extends TargetImpl { if (!amountWasSet) { setAmount(source, game); } - chosen = isChosen(game); + while (remainingAmount > 0) { + chosen = false; if (!player.canRespond()) { - return chosen; + chosen = isChosen(game); + break; } if (!getTargetController(game, playerId).chooseTargetAmount(outcome, this, source, game)) { - return chosen; + chosen = isChosen(game); + break; } chosen = isChosen(game); } - return chosen; + + return isChosen(game); } @Override diff --git a/Mage/src/main/java/mage/target/TargetCard.java b/Mage/src/main/java/mage/target/TargetCard.java index 7e6e8d5a5ac..b5322d2782d 100644 --- a/Mage/src/main/java/mage/target/TargetCard.java +++ b/Mage/src/main/java/mage/target/TargetCard.java @@ -50,6 +50,12 @@ public class TargetCard extends TargetObject { return this.filter; } + @Override + public TargetCard withNotTarget(boolean notTarget) { + super.withNotTarget(notTarget); + return this; + } + /** * Checks if there are enough {@link Card cards} in the appropriate zone that the player can choose from among them * or if they are autochosen since there are fewer than the minimum number. @@ -63,7 +69,7 @@ public class TargetCard extends TargetObject { public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { UUID sourceId = source != null ? source.getSourceId() : null; int possibleTargets = 0; - if (getNumberOfTargets() == 0) { // if 0 target is valid, the canChoose is always true + if (getMinNumberOfTargets() == 0) { // if 0 target is valid, the canChoose is always true return true; } for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) { diff --git a/Mage/src/main/java/mage/target/TargetImpl.java b/Mage/src/main/java/mage/target/TargetImpl.java index 0aae5458609..8c5f1f73ea2 100644 --- a/Mage/src/main/java/mage/target/TargetImpl.java +++ b/Mage/src/main/java/mage/target/TargetImpl.java @@ -31,7 +31,16 @@ public abstract class TargetImpl implements Target { protected int minNumberOfTargets; protected boolean required = true; protected boolean requiredExplicitlySet = false; + /** + * Simple chosen state due selected and require targets count + * Complex targets must override isChosen method or set chosen value manually + * For "up to" targets it will be false before first choose dialog: + * - chosen = false - player do not make a choice yet + * - chosen = true, targets.size = 0 - player choose 0 targets in "up to" and it's valid + * - chosen = true, targets.size >= 1 - player choose some targets and it's valid + */ protected boolean chosen = false; + // is the target handled as targeted spell/ability (notTarget = true is used for not targeted effects like e.g. sacrifice) protected boolean notTarget = false; protected boolean atRandom = false; // for inner choose logic @@ -72,11 +81,6 @@ public abstract class TargetImpl implements Target { this.shouldReportEvents = target.shouldReportEvents; } - @Override - public int getNumberOfTargets() { - return this.minNumberOfTargets; - } - @Override public int getMinNumberOfTargets() { return this.minNumberOfTargets; @@ -169,11 +173,14 @@ public abstract class TargetImpl implements Target { if (getMaxNumberOfTargets() != 1) { StringBuilder sb = new StringBuilder(); sb.append("Select ").append(targetName); + sb.append(" (selected ").append(targets.size()); if (getMaxNumberOfTargets() > 0 && getMaxNumberOfTargets() != Integer.MAX_VALUE) { - sb.append(" (selected ").append(targets.size()).append(" of ").append(getMaxNumberOfTargets()).append(')'); - } else { - sb.append(" (selected ").append(targets.size()).append(')'); + sb.append(" of ").append(getMaxNumberOfTargets()); } + if (getMinNumberOfTargets() > 0) { + sb.append(", min ").append(getMinNumberOfTargets()); + } + sb.append(')'); sb.append(suffix); return sb.toString(); } @@ -234,15 +241,64 @@ public abstract class TargetImpl implements Target { @Override public boolean isChosen(Game game) { - if (getMaxNumberOfTargets() == 0 && getNumberOfTargets() == 0) { + // min = max = 0 - for abilities with X=0, e.g. nothing to choose + if (getMaxNumberOfTargets() == 0 && getMinNumberOfTargets() == 0) { return true; } - return getMaxNumberOfTargets() != 0 && targets.size() == getMaxNumberOfTargets() || chosen; + + // limit by max amount + if (getMaxNumberOfTargets() > 0 && targets.size() > getMaxNumberOfTargets()) { + return false; + } + + // limit by min amount + if (getMinNumberOfTargets() > 0 && targets.size() < getMinNumberOfTargets()) { + return false; + } + + // all fine + return chosen || (targets.size() >= getMinNumberOfTargets() && targets.size() <= getMaxNumberOfTargets()); } @Override - public boolean doneChoosing(Game game) { - return getMaxNumberOfTargets() != 0 && targets.size() == getMaxNumberOfTargets(); + @Deprecated // TODO: replace usage in cards by full version from choose methods + public boolean isChoiceCompleted(Game game) { + return isChoiceCompleted(null, null, game); + } + + @Override + public boolean isChoiceCompleted(UUID abilityControllerId, Ability source, Game game) { + // make sure target request called one time minimum (for "up to" targets) + // choice is selected after any addTarget call (by test, AI or human players) + if (!isChoiceSelected()) { + return false; + } + + // make sure selected targets are valid + if (!isChosen(game)) { + return false; + } + + // make sure to auto-finish on all targets selection + // - human player can select and deselect targets until fill all targets amount or press done button + // - AI player can select all new targets as much as possible + if (getMaxNumberOfTargets() > 0) { + if (getMaxNumberOfTargets() == Integer.MAX_VALUE) { + if (abilityControllerId != null && source != null) { + // any amount - nothing to choose + return this.getSize() >= this.possibleTargets(abilityControllerId, source, game).size(); + } else { + // any amount - any selected + return this.getSize() > 0; + } + } else { + // check selected limit + return this.getSize() >= getMaxNumberOfTargets(); + } + } + + // all other use cases are fine + return true; } @Override @@ -252,13 +308,19 @@ public abstract class TargetImpl implements Target { chosen = false; } + @Override + public boolean isChoiceSelected() { + // min = max = 0 - for abilities with X=0, e.g. nothing to choose + return chosen || getMaxNumberOfTargets() == 0 && getMinNumberOfTargets() == 0; + } + @Override public void add(UUID id, Game game) { if (getMaxNumberOfTargets() == 0 || targets.size() < getMaxNumberOfTargets()) { if (!targets.containsKey(id)) { targets.put(id, 0); rememberZoneChangeCounter(id, game); - chosen = targets.size() >= getNumberOfTargets(); + chosen = isChosen(game); } } } @@ -266,7 +328,7 @@ public abstract class TargetImpl implements Target { @Override public void remove(UUID id) { if (targets.containsKey(id)) { - targets.remove(id); + targets.remove(id); // TODO: miss chosen update here? zoneChangeCounters.remove(id); } } @@ -285,13 +347,15 @@ public abstract class TargetImpl implements Target { if (!game.replaceEvent(new TargetEvent(id, source))) { targets.put(id, 0); rememberZoneChangeCounter(id, game); - chosen = targets.size() >= getNumberOfTargets(); + chosen = isChosen(game); if (!skipEvent && shouldReportEvents) { game.addSimultaneousEvent(GameEvent.getEvent(GameEvent.EventType.TARGETED, id, source, source.getControllerId())); } } } else { targets.put(id, 0); + rememberZoneChangeCounter(id, game); + chosen = isChosen(game); } } } @@ -323,14 +387,16 @@ public abstract class TargetImpl implements Target { if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.TARGET, id, source, source.getControllerId()))) { targets.put(id, amount); rememberZoneChangeCounter(id, game); - chosen = targets.size() >= getNumberOfTargets(); + chosen = isChosen(game); if (!skipEvent && shouldReportEvents) { game.fireEvent(GameEvent.getEvent(GameEvent.EventType.TARGETED, id, source, source.getControllerId())); } } } else { + // AI targets simulation targets.put(id, amount); rememberZoneChangeCounter(id, game); + chosen = isChosen(game); } } @@ -341,17 +407,45 @@ public abstract class TargetImpl implements Target { return false; } - chosen = targets.size() >= getNumberOfTargets(); + UUID abilityControllerId = playerId; + if (this.getTargetController() != null && this.getAbilityController() != null) { + abilityControllerId = this.getAbilityController(); + } + + chosen = false; do { + int prevTargetsCount = this.getTargets().size(); + + // stop by disconnect if (!targetController.canRespond()) { - return chosen; + break; } + + // stop by cancel/done if (!targetController.choose(outcome, this, source, game)) { - return chosen; + break; } - chosen = targets.size() >= getNumberOfTargets(); - } while (!isChosen(game) && !doneChoosing(game)); - return chosen; + + // TODO: miss auto-choose code? see chooseTarget below + // TODO: miss random code? see chooseTarget below + + chosen = isChosen(game); + + // stop by full complete + if (isChoiceCompleted(abilityControllerId, source, game)) { + break; + } + + // stop by nothing to use (actual for human and done button) + if (prevTargetsCount == this.getTargets().size()) { + break; + } + + // can select next target + } while (true); + + chosen = isChosen(game); + return this.getTargets().size() > 0; } @Override @@ -361,41 +455,80 @@ public abstract class TargetImpl implements Target { return false; } - List possibleTargets = new ArrayList<>(possibleTargets(playerId, source, game)); + UUID abilityControllerId = playerId; + if (this.getTargetController() != null && this.getAbilityController() != null) { + abilityControllerId = this.getAbilityController(); + } - chosen = targets.size() >= getNumberOfTargets(); + List randomPossibleTargets = new ArrayList<>(possibleTargets(playerId, source, game)); + + chosen = false; do { + int prevTargetsCount = this.getTargets().size(); + + // stop by disconnect if (!targetController.canRespond()) { - return chosen; + break; } + + // MAKE A CHOICE if (isRandom()) { - if (possibleTargets.isEmpty()) { - return chosen; + // random choice + + // stop on nothing to choose + if (randomPossibleTargets.isEmpty()) { + break; } - // find valid target - while (!possibleTargets.isEmpty()) { - int index = RandomUtil.nextInt(possibleTargets.size()); - if (this.canTarget(playerId, possibleTargets.get(index), source, game)) { - this.addTarget(possibleTargets.get(index), source, game); - possibleTargets.remove(index); + + // add valid random target one by one + while (!randomPossibleTargets.isEmpty()) { + UUID possibleTarget = RandomUtil.randomFromCollection(randomPossibleTargets); + if (this.canTarget(playerId, possibleTarget, source, game) && !this.contains(possibleTarget)) { + this.addTarget(possibleTarget, source, game); + randomPossibleTargets.remove(possibleTarget); break; } else { - possibleTargets.remove(index); + randomPossibleTargets.remove(possibleTarget); } } + // continue to next target } else { - // Try to autochoosen + // player's choice + UUID autoChosenId = tryToAutoChoose(playerId, source, game); - if (autoChosenId != null) { + if (autoChosenId != null && !this.contains(autoChosenId)) { + // auto-choose addTarget(autoChosenId, source, game); - } else if (!targetController.chooseTarget(outcome, this, source, game)) { // If couldn't autochoose ask player - return chosen; + // continue to next target (example: auto-choose must fill min/max = 2 from 2 possible cards) + } else { + // manual + + // stop by cancel/done + if (!targetController.chooseTarget(outcome, this, source, game)) { + break; + } + + // continue to next target } } - chosen = targets.size() >= getNumberOfTargets(); - } while (!isChosen(game) && !doneChoosing(game)); - return chosen; + chosen = isChosen(game); + + // stop by full complete + if (isChoiceCompleted(abilityControllerId, source, game)) { + break; + } + + // stop by nothing to choose (actual for human and done button?) + if (prevTargetsCount == this.getTargets().size()) { + break; + } + + // can select next target + } while (true); + + chosen = isChosen(game); + return this.getTargets().size() > 0; } @Override @@ -438,7 +571,7 @@ public abstract class TargetImpl implements Target { return false; } // if no targets have to be set and no targets are set, that's legal - if (getNumberOfTargets() == 0) { + if (getMinNumberOfTargets() == 0) { return true; } } @@ -457,7 +590,7 @@ public abstract class TargetImpl implements Target { // e.g. for {'A','B','C','D'} => N = 4 int N = possibleTargets.size(); // not enough targets, return no option - if (N < getNumberOfTargets()) { + if (N < getMinNumberOfTargets()) { return options; } // not target but that's allowed, return one empty option @@ -479,8 +612,8 @@ public abstract class TargetImpl implements Target { if (N < maxK) { // less possible targets than the maximum allowed so reduce the max maxK = N; } - int minK = getNumberOfTargets(); - if (getNumberOfTargets() == 0) { // add option without targets if possible + int minK = getMinNumberOfTargets(); + if (getMinNumberOfTargets() == 0) { // add option without targets if possible TargetImpl target = this.copy(); options.add(target); minK = 1; @@ -634,6 +767,7 @@ public abstract class TargetImpl implements Target { public void setTargetAmount(UUID targetId, int amount, Game game) { targets.put(targetId, amount); rememberZoneChangeCounter(targetId, game); + chosen = isChosen(game); } @Override @@ -685,10 +819,21 @@ public abstract class TargetImpl implements Target { } else { playerAutoTargetLevel = 2; } + + // freeze protection on disconnect - auto-choice works for online players only + boolean isOnline = player.canRespond(); + if (!player.isGameUnderControl()) { + Player controllingPlayer = game.getPlayer(player.getTurnControlledBy()); + if (player.isHuman()) { + isOnline = controllingPlayer.canRespond(); + } + } + String abilityText = source.getRule(true).toLowerCase(); boolean strictModeEnabled = player.getStrictChooseMode(); boolean canAutoChoose = this.getMinNumberOfTargets() == this.getMaxNumberOfTargets() // Targets must be picked - && possibleTargets.size() == this.getNumberOfTargets() - this.getSize() // Available targets are equal to the number that must be picked + && isOnline + && possibleTargets.size() == this.getMinNumberOfTargets() - this.getSize() // Available targets are equal to the number that must be picked && !strictModeEnabled // Test AI is not set to strictChooseMode(true) && playerAutoTargetLevel > 0 // Human player has enabled auto-choose in settings && !abilityText.contains("search"); // Do not autochoose for any effects which involve searching @@ -744,4 +889,13 @@ public abstract class TargetImpl implements Target { return null; } + + @Override + public String toString() { + return this.getClass().getSimpleName() + + ", from " + this.getMinNumberOfTargets() + + " to " + this.getMaxNumberOfTargets() + + ", " + this.getDescription() + + ", selected " + this.getTargets().size(); + } } diff --git a/Mage/src/main/java/mage/target/TargetPlayer.java b/Mage/src/main/java/mage/target/TargetPlayer.java index ba5327001b7..9c1c13ad722 100644 --- a/Mage/src/main/java/mage/target/TargetPlayer.java +++ b/Mage/src/main/java/mage/target/TargetPlayer.java @@ -132,7 +132,7 @@ public class TargetPlayer extends TargetImpl { @Override public boolean isLegal(Ability source, Game game) { //20101001 - 608.2b - if (getNumberOfTargets() == 0 && targets.isEmpty()) { + if (getMinNumberOfTargets() == 0 && targets.isEmpty()) { return true; // 0 targets selected is valid } return targets.keySet().stream().anyMatch(playerId -> canTarget(playerId, source, game)); diff --git a/Mage/src/main/java/mage/target/Targets.java b/Mage/src/main/java/mage/target/Targets.java index 7499467e02c..3be408aa038 100644 --- a/Mage/src/main/java/mage/target/Targets.java +++ b/Mage/src/main/java/mage/target/Targets.java @@ -1,10 +1,13 @@ package mage.target; +import mage.MageObject; import mage.abilities.Ability; import mage.constants.Outcome; import mage.game.Game; import mage.game.events.GameEvent; +import mage.players.Player; import mage.util.Copyable; +import mage.util.DebugUtil; import java.util.*; import java.util.stream.Collectors; @@ -37,8 +40,19 @@ public class Targets extends ArrayList implements Copyable { return this; } - public List getUnchosen(Game game) { - return stream().filter(target -> !target.isChosen(game)).collect(Collectors.toList()); + public Target getNextUnchosen(Game game) { + return getNextUnchosen(game, 0); + } + + public Target getNextUnchosen(Game game, int unchosenIndex) { + List res = stream() + .filter(target -> !target.isChoiceSelected()) + .collect(Collectors.toList()); + return unchosenIndex < res.size() ? res.get(unchosenIndex) : null; + } + + public boolean isChoiceCompleted(Game game) { + return stream().allMatch(t -> t.isChoiceCompleted(game)); } public void clearChosen() { @@ -52,59 +66,145 @@ public class Targets extends ArrayList implements Copyable { } public boolean choose(Outcome outcome, UUID playerId, UUID sourceId, Ability source, Game game) { - if (this.size() > 0) { - if (!canChoose(playerId, source, game)) { - return false; - } - while (!isChosen(game)) { - Target target = this.getUnchosen(game).get(0); - if (!target.choose(outcome, playerId, sourceId, source, game)) { - return false; - } - } + Player player = game.getPlayer(playerId); + if (player == null) { + return false; } - return true; + + // in test mode some targets can be predefined already, e.g. by cast/activate command + // so do not clear chosen status here + + if (this.size() > 0) { + do { + // stop on disconnect or nothing to choose + if (!player.canRespond() || !canChoose(playerId, source, game)) { + break; + } + + // stop on complete + Target target = this.getNextUnchosen(game); + if (target == null) { + break; + } + + // stop on cancel/done + if (!target.choose(outcome, playerId, sourceId, source, game)) { + if (!target.isChosen(game)) { + break; + } + } + + // target done, can take next one + } while (true); + } + + if (DebugUtil.GAME_SHOW_CHOOSE_TARGET_LOGS && !game.isSimulation()) { + printDebugTargets("choose finish", this, source, game); + } + + return isChosen(game); } public boolean chooseTargets(Outcome outcome, UUID playerId, Ability source, boolean noMana, Game game, boolean canCancel) { - if (this.size() > 0) { - if (!canChoose(playerId, source, game)) { - return false; - } + Player player = game.getPlayer(playerId); + if (player == null) { + return false; + } - //int state = game.bookmarkState(); - while (!isChosen(game)) { - Target target = this.getUnchosen(game).get(0); - UUID targetController = playerId; + // in test mode some targets can be predefined already, e.g. by cast/activate command + // so do not clear chosen status here + + if (this.size() > 0) { + do { + // stop on disconnect or nothing to choose + if (!player.canRespond() || !canChoose(playerId, source, game)) { + break; + } + + // stop on complete + Target target = this.getNextUnchosen(game); + if (target == null) { + break; + } // some targets can have controller different than ability controller + UUID targetController = playerId; if (target.getTargetController() != null) { targetController = target.getTargetController(); } - // if cast without mana (e.g. by suspend you may not be able to cancel the casting if you are able to cast it + // disable cancel button - if cast without mana (e.g. by suspend you may not be able to cancel the casting if you are able to cast it if (noMana) { target.setRequired(true); } - - // can be cancel by user + // enable cancel button if (canCancel) { target.setRequired(false); } - // make response checks + // stop on cancel/done if (!target.chooseTarget(outcome, targetController, source, game)) { - return false; + if (!target.isChosen(game)) { + break; + } } - // Check if there are some rules for targets are violated, if so reset the targets and start again - if (this.getUnchosen(game).isEmpty() + + // reset on wrong restrictions and start from scratch + if (this.getNextUnchosen(game) == null && game.replaceEvent(new GameEvent(GameEvent.EventType.TARGETS_VALID, source.getSourceId(), source, source.getControllerId()), source)) { - //game.restoreState(state, "Targets"); clearChosen(); } + + // target done, can take next one + } while (true); + } + + if (DebugUtil.GAME_SHOW_CHOOSE_TARGET_LOGS && !game.isSimulation()) { + printDebugTargets("chooseTargets finish", this, source, game); + } + + return isChosen(game); + } + + public static void printDebugTargets(String name, Targets targets, Ability source, Game game) { + List output = new ArrayList<>(); + printDebugTargets(name, targets, source, game, output); + output.forEach(System.out::println); + } + + public static void printDebugTargets(String name, Targets targets, Ability source, Game game, List output) { + output.add(""); + output.add(name + ":"); + output.add(String.format("* chosen: %s", targets.isChosen(game) ? "yes" : "no")); + output.add(String.format("* ability: %s", source)); + for (int i = 0; i < targets.size(); i++) { + Target target = targets.get(i); + output.add(String.format("* target %d: %s", i + 1, target)); + if (target.getTargets().isEmpty()) { + output.add(" - no choices"); + } else { + for (int j = 0; j < target.getTargets().size(); j++) { + UUID targetId = target.getTargets().get(j); + String targetInfo; + Player targetPlayer = game.getPlayer(targetId); + if (targetPlayer != null) { + targetInfo = targetPlayer.toString(); + } else { + MageObject targetObject = game.getObject(targetId); + if (targetObject != null) { + targetInfo = targetObject.toString(); + } else { + targetInfo = "unknown " + targetId; + } + } + if (target instanceof TargetAmount) { + output.add(String.format(" - choice %d.%d: amount %d, %s", i + 1, j + 1, target.getTargetAmount(targetId), targetInfo)); + } else { + output.add(String.format(" - choice %d.%d: %s", i + 1, j + 1, targetInfo)); + } + } } } - return true; } public boolean stillLegal(Ability source, Game game) { diff --git a/Mage/src/main/java/mage/target/common/TargetCardInLibrary.java b/Mage/src/main/java/mage/target/common/TargetCardInLibrary.java index 0b4735a40c6..cfb91500bec 100644 --- a/Mage/src/main/java/mage/target/common/TargetCardInLibrary.java +++ b/Mage/src/main/java/mage/target/common/TargetCardInLibrary.java @@ -76,17 +76,44 @@ public class TargetCardInLibrary extends TargetCard { Cards cardsId = new CardsImpl(); cards.forEach(cardsId::add); - chosen = targets.size() >= getMinNumberOfTargets(); + UUID abilityControllerId = playerId; + if (this.getTargetController() != null && this.getAbilityController() != null) { + abilityControllerId = this.getAbilityController(); + } + + chosen = false; do { + int prevTargetsCount = this.getTargets().size(); + + // stop by disconnect if (!player.canRespond()) { - return chosen; + break; } + + // stop by cancel/done + // TODO: need research - why it used chooseTarget instead choose? Need random and other options? + // someday must be replaced by player.choose (require whole tests fix from addTarget to setChoice) if (!player.chooseTarget(outcome, cardsId, this, source, game)) { return chosen; } - chosen = targets.size() >= getMinNumberOfTargets(); - } while (!isChosen(game) && !doneChoosing(game)); - return chosen; + + chosen = isChosen(game); + + // stop by full complete + if (isChoiceCompleted(abilityControllerId, source, game)) { + break; + } + + // stop by nothing to use (actual for human and done button) + if (prevTargetsCount == this.getTargets().size()) { + break; + } + + // can select next target + } while (true); + + chosen = isChosen(game); + return this.getTargets().size() > 0; } @Override diff --git a/Mage/src/main/java/mage/target/common/TargetCardInOpponentsGraveyard.java b/Mage/src/main/java/mage/target/common/TargetCardInOpponentsGraveyard.java index 3a5f8fda2a9..e7f81c810ef 100644 --- a/Mage/src/main/java/mage/target/common/TargetCardInOpponentsGraveyard.java +++ b/Mage/src/main/java/mage/target/common/TargetCardInOpponentsGraveyard.java @@ -86,7 +86,7 @@ public class TargetCardInOpponentsGraveyard extends TargetCard { @Override public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { int possibleTargets = 0; - if (getNumberOfTargets() == 0) { // if 0 target is valid, the canChoose is always true + if (getMinNumberOfTargets() == 0) { // if 0 target is valid, the canChoose is always true return true; } Player sourceController = game.getPlayer(sourceControllerId); diff --git a/Mage/src/main/java/mage/target/common/TargetCreaturePermanentSameController.java b/Mage/src/main/java/mage/target/common/TargetCreaturePermanentSameController.java deleted file mode 100644 index d0ea77eb906..00000000000 --- a/Mage/src/main/java/mage/target/common/TargetCreaturePermanentSameController.java +++ /dev/null @@ -1,54 +0,0 @@ -package mage.target.common; - -import mage.abilities.Ability; -import mage.filter.StaticFilters; -import mage.filter.common.FilterCreaturePermanent; -import mage.game.Game; -import mage.game.permanent.Permanent; - -import java.util.UUID; - -/** - * @author LevelX2 - */ -public class TargetCreaturePermanentSameController extends TargetCreaturePermanent { - - public TargetCreaturePermanentSameController(int numTargets) { - this(numTargets, StaticFilters.FILTER_PERMANENT_CREATURE); - } - - public TargetCreaturePermanentSameController(int numTargets, FilterCreaturePermanent filter) { - super(numTargets, numTargets, filter, false); - } - - protected TargetCreaturePermanentSameController(final TargetCreaturePermanentSameController target) { - super(target); - } - - @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (super.canTarget(controllerId, id, source, game)) { - Permanent firstTargetPermanent = game.getPermanent(id); - if (firstTargetPermanent != null) { - for (Object object : getTargets()) { - UUID targetId = (UUID) object; - Permanent targetPermanent = game.getPermanent(targetId); - if (targetPermanent != null) { - if (!firstTargetPermanent.getId().equals(targetPermanent.getId())) { - if (!firstTargetPermanent.isControlledBy(targetPermanent.getOwnerId())) { - return false; - } - } - } - } - return true; - } - } - return false; - } - - @Override - public TargetCreaturePermanentSameController copy() { - return new TargetCreaturePermanentSameController(this); - } -} diff --git a/Mage/src/main/java/mage/target/common/TargetPermanentOrPlayer.java b/Mage/src/main/java/mage/target/common/TargetPermanentOrPlayer.java index ca6c0e573f2..6cc048b56ff 100644 --- a/Mage/src/main/java/mage/target/common/TargetPermanentOrPlayer.java +++ b/Mage/src/main/java/mage/target/common/TargetPermanentOrPlayer.java @@ -133,7 +133,7 @@ public class TargetPermanentOrPlayer extends TargetImpl { } } } - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter.getPermanentFilter(), sourceControllerId, game)) { + for (Permanent permanent : game.getBattlefield().getActivePermanents(filter.getPermanentFilter(), sourceControllerId, game)) { // TODO: miss source? if (permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game) && filter.match(permanent, sourceControllerId, source, game)) { count++; if (count >= this.minNumberOfTargets) { diff --git a/Mage/src/main/java/mage/target/common/TargetPermanentSameController.java b/Mage/src/main/java/mage/target/common/TargetPermanentSameController.java new file mode 100644 index 00000000000..9dd16fff7e8 --- /dev/null +++ b/Mage/src/main/java/mage/target/common/TargetPermanentSameController.java @@ -0,0 +1,54 @@ +package mage.target.common; + +import mage.abilities.Ability; +import mage.filter.FilterPermanent; +import mage.game.Controllable; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; + +import java.util.Objects; +import java.util.UUID; + +/** + * @author LevelX2 + */ +public class TargetPermanentSameController extends TargetPermanent { + + public TargetPermanentSameController(FilterPermanent filter) { + this(2, filter); + } + + public TargetPermanentSameController(int numTargets, FilterPermanent filter) { + super(numTargets, numTargets, filter, false); + } + + protected TargetPermanentSameController(final TargetPermanentSameController target) { + super(target); + } + + @Override + public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { + if (!super.canTarget(controllerId, id, source, game)) { + return false; + } + if (this.getTargets().isEmpty()) { + return true; + } + Permanent firstTargetPermanent = game.getPermanent(id); + return firstTargetPermanent != null + && this + .getTargets() + .stream() + .map(game::getPermanent) + .filter(Objects::nonNull) + .filter(permanent -> !permanent.equals(firstTargetPermanent)) + .map(Controllable::getControllerId) + .allMatch(firstTargetPermanent::isControlledBy); + } + + @Override + public TargetPermanentSameController copy() { + return new TargetPermanentSameController(this); + } +} diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java index e7bb1732f4d..5c32c6b6d3a 100644 --- a/Mage/src/main/java/mage/util/CardUtil.java +++ b/Mage/src/main/java/mage/util/CardUtil.java @@ -58,6 +58,9 @@ import java.net.URLDecoder; import java.net.URLEncoder; import java.text.SimpleDateFormat; import java.util.*; +import java.util.function.BiFunction; +import java.util.function.BiPredicate; +import java.util.function.Function; import java.util.function.ToIntFunction; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -1131,10 +1134,10 @@ public final class CardUtil { * Also ensures that spells/abilities that target the same object twice only trigger each "becomes the target" ability once. * If this is the first attempt at triggering for a given ability targeting a given object, * this method records that in the game state for later checks by this same method, to not return the same object again. - * + * * @param checkingReference must be unique for each usage (this.getId().toString() of the TriggeredAbility, or this.getKey() of the watcher) - * @param event the GameEvent.EventType.TARGETED from checkTrigger() or watch() - * @param game the Game from checkTrigger() or watch() + * @param event the GameEvent.EventType.TARGETED from checkTrigger() or watch() + * @param game the Game from checkTrigger() or watch() * @return the StackObject which targeted the source, or null if already used or not found */ public static StackObject findTargetingStackObject(String checkingReference, GameEvent event, Game game) { @@ -2213,17 +2216,13 @@ public final class CardUtil { return sb.toString(); } - public static T getEffectValueFromAbility(Ability ability, String key, Class clazz) { - return getEffectValueFromAbility(ability, key, clazz, null); - } - - public static T getEffectValueFromAbility(Ability ability, String key, Class clazz, T defaultValue) { + public static Optional getEffectValueFromAbility(Ability ability, String key, Class clazz) { return castStream( ability.getAllEffects() .stream() .map(effect -> effect.getValue(key)), clazz - ).findFirst().orElse(defaultValue); + ).findFirst(); } public static Stream castStream(Collection collection, Class clazz) { @@ -2234,6 +2233,71 @@ public final class CardUtil { return stream.filter(clazz::isInstance).map(clazz::cast).filter(Objects::nonNull); } + public static boolean checkAnyPairs(Collection collection, BiPredicate predicate) { + return streamPairsWithMap(collection, (t1, t2) -> predicate.test(t1, t2)).anyMatch(x -> x); + } + + public static Stream streamAllPairwiseMatches(Collection collection, BiPredicate predicate) { + return streamPairsWithMap( + collection, + (t1, t2) -> predicate.test(t1, t2) + ? Stream.of(t1, t2) + : Stream.empty() + ).flatMap(Function.identity()).distinct(); + } + + private static class IntPairIterator implements Iterator> { + private final int amount; + private int firstCounter = 0; + private int secondCounter = 1; + + IntPairIterator(int amount) { + this.amount = amount; + } + + @Override + public boolean hasNext() { + return firstCounter + 1 < amount; + } + + @Override + public AbstractMap.SimpleImmutableEntry next() { + AbstractMap.SimpleImmutableEntry value + = new AbstractMap.SimpleImmutableEntry(firstCounter, secondCounter); + secondCounter++; + if (secondCounter == amount) { + firstCounter++; + secondCounter = firstCounter + 1; + } + return value; + } + + public int getMax() { + // amount choose 2 + return (amount * amount - amount) / 2; + } + } + + public static Stream streamPairsWithMap(Collection collection, BiFunction function) { + if (collection.size() < 2) { + return Stream.empty(); + } + List list; + if (collection instanceof List) { + list = (List) collection; + } else { + list = new ArrayList<>(collection); + } + IntPairIterator it = new IntPairIterator(list.size()); + return Stream + .generate(it::next) + .limit(it.getMax()) + .map(pair -> function.apply( + list.get(pair.getKey()), + list.get(pair.getValue()) + )); + } + public static void AssertNoControllerOwnerPredicates(Target target) { List list = new ArrayList<>(); Predicates.collectAllComponents(target.getFilter().getPredicates(), target.getFilter().getExtraPredicates(), list); diff --git a/Mage/src/main/java/mage/util/DebugUtil.java b/Mage/src/main/java/mage/util/DebugUtil.java index 09ba1e89659..4c090a4557e 100644 --- a/Mage/src/main/java/mage/util/DebugUtil.java +++ b/Mage/src/main/java/mage/util/DebugUtil.java @@ -17,6 +17,11 @@ public class DebugUtil { public static boolean AI_ENABLE_DEBUG_MODE = false; public static boolean AI_SHOW_TARGET_OPTIMIZATION_LOGS = false; // works with target amount + // GAME + // print detail target info for activate/cast/trigger only, not a single choose dialog + // can be useful to debug unit tests, auto-choose or AI + public static boolean GAME_SHOW_CHOOSE_TARGET_LOGS = false; + // cards basic (card panels) public static boolean GUI_CARD_DRAW_OUTER_BORDER = false; public static boolean GUI_CARD_DRAW_INNER_BORDER = false; diff --git a/Mage/src/main/java/mage/util/ManaUtil.java b/Mage/src/main/java/mage/util/ManaUtil.java index 816353e13e9..f86f376a1d0 100644 --- a/Mage/src/main/java/mage/util/ManaUtil.java +++ b/Mage/src/main/java/mage/util/ManaUtil.java @@ -710,7 +710,7 @@ public final class ManaUtil { int bookmark = game.bookmarkState(); player.resetStoredBookmark(game); - wantToPay = player.announceXMana(0, maxValue, "Choose how much mana to pay", game, source); + wantToPay = player.announceX(0, maxValue, "Choose how much mana to pay", game, source, true); if (wantToPay > 0) { Cost cost = ManaUtil.createManaCost(wantToPay, payAsX); payed = cost.pay(source, game, source, player.getId(), false, null); diff --git a/Mage/src/main/java/mage/watchers/common/PlayerLostGameWatcher.java b/Mage/src/main/java/mage/watchers/common/PlayerLostGameWatcher.java new file mode 100644 index 00000000000..59a3c82eb6e --- /dev/null +++ b/Mage/src/main/java/mage/watchers/common/PlayerLostGameWatcher.java @@ -0,0 +1,43 @@ +package mage.watchers.common; + +import mage.constants.WatcherScope; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.watchers.Watcher; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public class PlayerLostGameWatcher extends Watcher { + + private final Set players = new HashSet<>(); + + public PlayerLostGameWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + switch (event.getType()) { + case BEGINNING_PHASE_PRE: + if (game.getTurnNum() == 1) { + players.clear(); + } + return; + case LOST: + players.add(event.getPlayerId()); + } + } + + public static int getCount(Game game) { + return game + .getState() + .getWatcher(PlayerLostGameWatcher.class) + .players + .size(); + } +} diff --git a/Mage/src/main/resources/tokens-database.txt b/Mage/src/main/resources/tokens-database.txt index 8dc1ecf4e63..696ad31ba28 100644 --- a/Mage/src/main/resources/tokens-database.txt +++ b/Mage/src/main/resources/tokens-database.txt @@ -2773,6 +2773,7 @@ # FIN |Generate|TOK:FIN|Food|||FoodToken| +|Generate|TOK:FIN|Hero|||HeroToken| # JVC |Generate|TOK:JVC|Elemental Shaman|||ElementalShamanToken| diff --git a/Utils/gen-card.pl b/Utils/gen-card.pl index e17ffc41f69..8760aba81c2 100755 --- a/Utils/gen-card.pl +++ b/Utils/gen-card.pl @@ -5,7 +5,6 @@ use Text::Template; use strict; - my $authorFile = 'author.txt'; my $dataFile = 'mtg-cards-data.txt'; my $setsFile = 'mtg-sets-data.txt'; @@ -32,7 +31,7 @@ sub fixCost { my $author; if (-e $authorFile) { - open (DATA, $authorFile) || die "can't open $authorFile : $!"; + open(DATA, $authorFile) || die "can't open $authorFile : $!"; $author = ; chomp $author; close(DATA); @@ -40,32 +39,32 @@ if (-e $authorFile) { $author = 'anonymous'; } -open (DATA, $dataFile) || die "can't open $dataFile : $!"; -while(my $line = ) { +open(DATA, $dataFile) || die "can't open $dataFile : $!"; +while (my $line = ) { my @data = split('\\|', $line); - $cards{$data[0]}{$data[1]} = \@data; + $cards{$data[0]}{$data[1]}{$data[2]} = \@data; } close(DATA); -open (DATA, $setsFile) || die "can't open $setsFile : $!"; -while(my $line = ) { +open(DATA, $setsFile) || die "can't open $setsFile : $!"; +while (my $line = ) { my @data = split('\\|', $line); - $sets{$data[0]}= $data[1]; + $sets{$data[0]} = $data[1]; #print "$data[0]--$data[1]\n" } close(DATA); -open (DATA, $knownSetsFile) || die "can't open $knownSetsFile : $!"; -while(my $line = ) { +open(DATA, $knownSetsFile) || die "can't open $knownSetsFile : $!"; +while (my $line = ) { my @data = split('\\|', $line); - $knownSets{$data[0]}= $data[1]; + $knownSets{$data[0]} = $data[1]; } close(DATA); -open (DATA, $keywordsFile) || die "can't open $keywordsFile : $!"; -while(my $line = ) { +open(DATA, $keywordsFile) || die "can't open $keywordsFile : $!"; +while (my $line = ) { my @data = split('\\|', $line); - $keywords{toCamelCase($data[0])}= $data[1]; + $keywords{toCamelCase($data[0])} = $data[1]; } close(DATA); @@ -97,12 +96,11 @@ if (!$cardName) { chomp $cardName; } - if (!exists $cards{$cardName}) { my $possible; - foreach $possible (sort keys (%cards)) { + foreach $possible (sort keys(%cards)) { if ($possible =~ m/$cardName/img && $cardName =~ m/..../) { - print ("Did you mean $possible?\n"); + print("Did you mean $possible?\n"); } } die "Card name doesn't exist: $cardName\n"; @@ -116,18 +114,18 @@ my $originalName = $cardName; # Remove the // from name of split cards if (index($cardName, $splitDelimiter) != -1) { - $cardName =~ s/$splitDelimiter/$empty/g; + $cardName =~ s/$splitDelimiter/$empty/g; $cardTemplate = 'cardSplitClass.tmpl'; $splitSpell = 'true'; } # Check if card is already implemented -my $fileName = "../Mage.Sets/src/mage/cards/".lc(substr($cardName, 0, 1))."/".toCamelCase($cardName).".java"; -if(-e $fileName) { - die "$cardName is already implemented.\n$fileName\n"; +my $fileName = "../Mage.Sets/src/mage/cards/" . lc(substr($cardName, 0, 1)) . "/" . toCamelCase($cardName) . ".java"; +if (-e $fileName) { + die "$cardName is already implemented.\n$fileName\n"; } - + # Generate lines to corresponding sets my %vars; $vars{'className'} = toCamelCase($cardName); @@ -135,45 +133,54 @@ $vars{'cardNameFirstLetter'} = lc substr($cardName, 0, 1); my @card; foreach my $setName (keys %{$cards{$originalName}}) { - my $setFileName = "../Mage.Sets/src/mage/sets/".$knownSets{$setName}.".java"; - @card = @{${cards{$originalName}{$setName}}}; - my $line = " cards.add(new SetCardInfo(\"".$card[0]."\", ".$card[2].", Rarity.".$raritiesConversion{$card[3]}.", mage.cards.".$vars{'cardNameFirstLetter'}.".".$vars{'className'}.".class));\n"; - @ARGV = ($setFileName); - $^I = '.bak'; - my $last; - my $lastName; - my $currName; - while (<>) { - if ($_ !~ m/cards.add/) { - if (defined($last)) { - print $last; - } - # print card line as last - if (defined($currName) && ($cardName cmp $currName) > 0) { - print $line; - undef $currName; - } - $last = $_; - print $last if eof; - next; + my $keyCount = keys %{$cards{$originalName}{$setName}}; + my $printingString; + if (($keyCount) > 1) { + $printingString = ", NON_FULL_USE_VARIOUS"; + } else { + $printingString = ""; } - - ($lastName) = $last =~ m/"(.*?)"/; - ($currName) = $_ =~ m/"(.*?)"/; - print $last; - $last = $_; - # print card line as first - if (!defined($lastName) && defined($currName) && ($currName cmp $cardName) > 0) { - print $line; - } - # print card line in the middle - if (defined($lastName) && defined($currName) && ($cardName cmp $lastName) > 0 && ($currName cmp $cardName) > 0) { - print $line; - } - } + foreach my $cardNumber (sort keys %{$cards{$originalName}{$setName}}) { + my $setFileName = "../Mage.Sets/src/mage/sets/" . $knownSets{$setName} . ".java"; + @card = @{${cards {$originalName}{ $setName }{$cardNumber}}}; + my $line = " cards.add(new SetCardInfo(\"" . $card[0] . "\", " . $card[2] . ", Rarity." . $raritiesConversion{$card[3]} . ", mage.cards." . $vars{'cardNameFirstLetter'} . "." . $vars{'className'} . ".class" . $printingString . "));\n"; + @ARGV = ($setFileName); + $^I = '.bak'; + my $last; + my $lastName; + my $currName; + while (<>) { + if ($_ !~ m/cards.add/) { + if (defined($last)) { + print $last; + } + # print card line as last + if (defined($currName) && ($cardName cmp $currName) > 0) { + print $line; + undef $currName; + } + $last = $_; + print $last if eof; + next; + } - unlink $setFileName.".bak"; - print "$setFileName\n"; + ($lastName) = $last =~ m/"(.*?)"/; + ($currName) = $_ =~ m/"(.*?)"/; + print $last; + $last = $_; + # print card line as first + if (!defined($lastName) && defined($currName) && ($currName cmp $cardName) > 0) { + print $line; + } + # print card line in the middle + if (defined($lastName) && defined($currName) && ($cardName cmp $lastName) > -1 && ($currName cmp $cardName) > 0) { + print $line; + } + } + + unlink $setFileName . ".bak"; + print "$setFileName\n"; + } } # Generate the card @@ -194,7 +201,7 @@ my $cardAbilities = $card[8]; my $type = $card[5]; while ($type =~ m/([a-zA-Z]+)( )*/g) { if (exists($cardTypes{$1})) { - push(@types, $cardTypes{$1}); + push(@types, $cardTypes{$1}); if ($cardTypes{$1} eq $cardTypes{'Planeswalker'}) { $vars{'planeswalker'} = 'true'; $cardAbilities = $card[7]; @@ -206,11 +213,11 @@ while ($type =~ m/([a-zA-Z]+)( )*/g) { if (@types) { my $st = uc($1); $vars{'subType'} .= "\n this.subtype.add(SubType.$st);"; - $vars{'hasSubTypes'} = 'true'; + $vars{'hasSubTypes'} = 'true'; } else { my $st = uc($1); $vars{'subType'} .= "\n this.supertype.add(SuperType.$st);"; - $vars{'hasSuperTypes'} = 'true'; + $vars{'hasSuperTypes'} = 'true'; } } } @@ -220,9 +227,9 @@ $vars{'abilitiesImports'} = ''; $vars{'abilities'} = ''; my $strong = ""; -$cardAbilities =~ s/$strong/$empty/g; +$cardAbilities =~ s/$strong/$empty/g; $strong = ""; -$cardAbilities =~ s/$strong/$empty/g; +$cardAbilities =~ s/$strong/$empty/g; $cardAbilities =~ s/\&/\|/g; my @abilities = split('\$', $cardAbilities); @@ -282,8 +289,8 @@ foreach my $ability (@abilities) { if ($1 =~ m/(^.*\s.*)/g) { $vars{'abilities'} .= "\n TargetPermanent auraTarget = new TargetPermanent(filter);"; } else { - $vars{'abilities'} .= "\n TargetPermanent auraTarget = new Target". toCamelCase($1) . "Permanent();"; - $vars{'abilitiesImports'} .= "\nimport mage.target.common.Target". toCamelCase($1) . "Permanent;"; + $vars{'abilities'} .= "\n TargetPermanent auraTarget = new Target" . toCamelCase($1) . "Permanent();"; + $vars{'abilitiesImports'} .= "\nimport mage.target.common.Target" . toCamelCase($1) . "Permanent;"; } $vars{'abilities'} .= "\n this.getSpellAbility().addTarget(auraTarget);"; $vars{'abilities'} .= "\n this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature));"; diff --git a/Utils/keywords.txt b/Utils/keywords.txt index fb6f00141ca..ea6b857b8ce 100644 --- a/Utils/keywords.txt +++ b/Utils/keywords.txt @@ -76,6 +76,7 @@ Intimidate|instance| Ingest|new| Islandcycling|cost| Islandwalk|new| +Job select|new| Jump-start|card| Kicker|manaString| Level up|cost| @@ -133,6 +134,7 @@ Suspend|number, cost, card| Swampcycling|cost| Swampwalk|new| Umbra armor|new| +Tiered|card| Toxic|number| Training|new| Trample|instance| diff --git a/Utils/mtg-cards-data.txt b/Utils/mtg-cards-data.txt index 811447a7747..0d939c4b9d5 100644 --- a/Utils/mtg-cards-data.txt +++ b/Utils/mtg-cards-data.txt @@ -57179,27 +57179,847 @@ Vineglimmer Snarl|Aetherdrift Commander|183|R||Land|||As Vineglimmer Snarl enter Yavimaya Coast|Aetherdrift Commander|184|R||Land|||{T}: Add {C}.${T}: Add {G} or {U}. Yavimaya Coast deals 1 damage to you.| Celes, Rune Knight|Final Fantasy Commander|1|M|{1}{R}{W}{B}|Legendary Creature - Human Wizard Knight|4|4|When Celes enters, discard any number of cards, then draw that many cards plus one.$Whenever one or more other creatures you control enter, if one or more of them entered from a graveyard or was cast from a graveyard, put a +1/+1 counter on each creature you control.| Cloud, Ex-SOLDIER|Final Fantasy Commander|2|M|{2}{R}{G}{W}|Legendary Creature - Human Soldier Mercenary|4|4|Haste$When Cloud enters, attach up to one target Equipment you control to it.$Whenever Cloud attacks, draw a card for each equipped attacking creature you control. Then if Cloud has power 7 or greater, create two Treasure tokens.| +G'raha Tia, Scion Reborn|Final Fantasy Commander|3|M|{W}{U}{B}|Legendary Creature - Cat Wizard|2|3|Lifelink$Throw Wide the Gates -- Whenever you cast a noncreature spell, you may pay X life, where X is that spell's mana value. If you do, create a 1/1 colorless Hero creature token and put X+1/+1 counters on it. Do this only once each turn.| Terra, Herald of Hope|Final Fantasy Commander|4|M|{R}{W}{B}|Legendary Creature - Human Wizard Warrior|3|3|Trance -- At the beginning of combat on your turn, mill two cards. Terra gains flying until end of turn.$Whenever Terra deals combat damage to a player, you may pay {2}. When you do, return target creature card with power 3 or less from your graveyard to the battlefield tapped.| -Tidus, Yuna's Guardian|Final Fantasy Commander|5|M|{G}{W}{U}|Legendary Creature - Human Warrior|3|3|At the beginning of combat on your turn, you may move a counter from target creature you control onto a second target creature you control.$Cheer - Whenever one or more creatures you control with counters on them deal combat damage to a player, you may draw a card and proliferate. Do this only once each turn.| +Tidus, Yuna's Guardian|Final Fantasy Commander|5|M|{G}{W}{U}|Legendary Creature - Human Warrior|3|3|At the beginning of combat on your turn, you may move a counter from target creature you control onto a second target creature you control.$Cheer -- Whenever one or more creatures you control with counters on them deal combat damage to a player, you may draw a card and proliferate. Do this only once each turn.| +Tifa, Martial Artist|Final Fantasy Commander|6|M|{1}{R}{G}{W}|Legendary Creature - Human Monk|4|4|Melee$Whenever one or more creatures you control with power 7 or greater deal combat damage to a player, untap all creatures you control. If it's the first combat phase of your turn, there is an additional combat phase after this phase.| Y'shtola, Night's Blessed|Final Fantasy Commander|7|M|{1}{W}{U}{B}|Legendary Creature - Cat Warlock|2|4|Vigilance$At the beginning of each end step, if a player lost 4 or more life this turn, you draw a card.$Whenever you cast a noncreature spell with mana value 3 or greater, Y'shtola deals 2 damage to each opponent and you gain 2 life.| +Yuna, Grand Summoner|Final Fantasy Commander|8|M|{1}{G}{W}{U}|Legendary Creature - Human Cleric|1|5|Grand Summon -- {T}: Add one mana of any color. When you next cast a creature spell this turn, that creature enters with two additional +1/+1 counters on it.$Whenever another permanent you control is put into a graveyard from the battlefield, if it had one or more counters on it, you may put that number of +1/+1 counters on target creature.| +Alisaie Leveilleur|Final Fantasy Commander|9|R|{2}{W}|Legendary Creature - Elf Wizard|3|2|Partner with Alphinaud Leveilleur$First strike$Dualcast -- The second spell you cast each turn costs {2} less to cast.| +Auron, Venerated Guardian|Final Fantasy Commander|10|R|{3}{W}|Legendary Creature - Human Spirit Samurai|2|5|Vigilance$Shooting Star -- Whenever Auron attacks, put a +1/+1 counter on it. When you do, exile target creature defending player controls with power less than Auron's power until Auron leaves the battlefield.| +Champions from Beyond|Final Fantasy Commander|11|R|{X}{W}{W}|Enchantment|||When this enchantment enters, create X 1/1 colorless Hero creature tokens.$Light Party -- Whenever you attack with four or more creatures, scry 2, then draw a card.$Full Party -- Whenever you attack with eight or more creatures, those creatures get +4/+4 until end of turn.| +Chocobo Knights|Final Fantasy Commander|12|R|{3}{W}|Creature - Human Knight|3|3|Whenever you attack, creatures you control with counters on them gain double strike until end of turn.| +Cid, Freeflier Pilot|Final Fantasy Commander|13|R|{1}{W}|Legendary Creature - Human Warrior Pilot|2|2|Equipment and Vehicle spells you cast cost {1} less to cast.$Jump -- During your turn, Cid has flying.${2},{T}: Return target Equipment or Vehicle card from your graveyard to your hand.| +Cloud's Limit Break|Final Fantasy Commander|14|R|{1}{W}|Instant|||Tiered$* Cross-Slash -- {0} -- Destroy target tapped creature.$* Blade Beam -- {1} -- Destroy any number of target tapped creatures with different controllers.$* Omnislash -- {3}{W} -- Destroy all tapped creatures.| +Coin of Fate|Final Fantasy Commander|15|R|{1}{W}|Artifact|||When this artifact enters, surveil 1.${3}{W},{T}, Exile two creature cards from your graveyard, Sacrifice this artifact: An opponent chooses one of the exiled cards. You put that card on the bottom of your library and return the other to the battlefield tapped. You become the monarch.| +Cyan, Vengeful Samurai|Final Fantasy Commander|16|R|{6}{W}|Legendary Creature - Human Samurai|3|3|This spell costs {1} less to cast for each creature card in your graveyard.$Double strike$Whenever one or more creature cards leave your graveyard, put a +1/+1 counter on Cyan.| +Dancer's Chakrams|Final Fantasy Commander|17|R|{3}{W}|Artifact - Equipment|||Job select$Equipped creature gets +2/+2, has lifelink and "Other commanders you control get +2/+2 and have lifelink," and is a Performer in addition to its other types.$Krishna -- Equip {3}| +Elena, Turk Recruit|Final Fantasy Commander|18|R|{2}{W}|Legendary Creature - Human Assassin|1|4|When Elena enters, return target non-Assassin historic card from your graveyard to your hand.$Whenever you cast a historic spell, put a +1/+1 counter on Elena.| +Gatta and Luzzu|Final Fantasy Commander|19|R|{2}{W}|Legendary Creature - Human Soldier|1|1|Flash$When Gatta and Luzzu enters, choose target creature you control. If damage would be dealt to that creature this turn, prevent that damage and put that many +1/+1 counters on it.| +General Leo Cristophe|Final Fantasy Commander|20|R|{4}{W}|Legendary Creature - Human Soldier|2|2|When General Leo Cristophe enters, return up to one target creature card with mana value 3 or less from your graveyard to the battlefield. Then put a +1/+1 counter on General Leo Cristophe for each creature you control.| +Heidegger, Shinra Executive|Final Fantasy Commander|21|R|{3}{W}|Legendary Creature - Human Soldier|3|3|At the beginning of combat on your turn, target creature you control gets +X/+0 until end of turn, where X is the number of Soldiers you control.$At the beginning of your end step, create a number of 1/1 white Soldier creature tokens equal to the number of opponents who control more creatures than you.| +Helitrooper|Final Fantasy Commander|22|R|{1}{W}|Creature - Human Soldier|1|2|Flying$Whenever this creature attacks, another target attacking creature gains flying until end of turn.$Equip abilities you activate that target this creature cost {2} less to activate.| +Lord Jyscal Guado|Final Fantasy Commander|23|R|{1}{W}|Legendary Creature - Spirit Cleric|2|1|Flying$At the beginning of each end step, if you put a counter on a creature this turn, investigate.| +Protection Magic|Final Fantasy Commander|24|R|{1}{W}|Instant|||Put a shield counter on each of up to three target creatures.| +SOLDIER Military Program|Final Fantasy Commander|25|R|{2}{W}|Enchantment|||At the beginning of combat on your turn, choose one. If you control a commander, you may choose both instead.$* Create a 1/1 white Soldier creature token.$* Put a +1/+1 counter on each of up to two Soldiers you control.| +Summon: Good King Mog XII|Final Fantasy Commander|26|R|{4}{W}|Enchantment Creature - Saga Moogle|4|4|(As this Saga enters and after your draw step, add a lore counter. Sacrifice after IV.)$I - Create two 1/2 white Moogle creature tokens with lifelink.$II, III - Whenever you cast a noncreature spell this turn, create a token that's a copy of a non-Saga token you control.$IV - Put two +1/+1 counters on each other Moogle you control.$Flying, lifelink| +Summon: Ixion|Final Fantasy Commander|27|R|{2}{W}|Enchantment Creature - Saga Unicorn|3|3|(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I - Aerospark -- Exile target creature an opponent controls until this Saga leaves the battlefield.$II, III - Put a +1/+1 counter on each of up to two target creatures you control. You gain 2 life.$First strike| +Summon: Yojimbo|Final Fantasy Commander|28|R|{3}{W}|Enchantment Creature - Saga Samurai|5|5|(As this Saga enters and after your draw step, add a lore counter. Sacrifice after IV.)$I - Exile target artifact, enchantment, or tapped creature an opponent controls.$II, III - Until your next turn, creatures can't attack you unless their controller pays {2} for each of those creatures.$IV - Create X Treasure tokens, where X is the number of opponents who control a creature with power 4 or greater.$Vigilance| +Summoner's Sending|Final Fantasy Commander|29|R|{1}{W}|Enchantment|||At the beginning of your end step, you may exile target creature card from a graveyard. If you do, create a 1/1 white Spirit creature token with flying. Put a +1/+1 counter on it if the exiled card's mana value is 4 or greater.| +Tataru Taru|Final Fantasy Commander|30|R|{1}{W}|Legendary Creature - Dwarf Advisor|0|3|When Tataru Taru enters, you draw a card and target opponent may draw a card.$Scions' Secretary -- Whenever an opponent draws a card, if it isn't that player's turn, create a tapped Treasure token. This ability triggers only once each turn.| +Thancred Waters|Final Fantasy Commander|31|R|{4}{W}|Legendary Creature - Human Warrior|3|5|Flash$Royal Guard -- When Thancred Waters enters, another target legendary permanent you control gains indestructible for as long as you control Thancred Waters.$Whenever you cast a noncreature spell, Thancred Waters gains indestructible until end of turn.| +Ultimate Magic: Holy|Final Fantasy Commander|32|R|{2}{W}|Instant|||Permanents you control gain indestructible until end of turn. If this spell was cast from exile, prevent all damage that would be dealt to you this turn.$Foretell {2}{W}| +Alphinaud Leveilleur|Final Fantasy Commander|33|R|{3}{U}|Legendary Creature - Elf Wizard|2|4|Partner with Alisaie Leveilleur$Vigilance$Eukrasia -- Whenever you cast your second spell each turn, draw a card.| +Blitzball Stadium|Final Fantasy Commander|34|R|{X}{U}|Artifact|||When this artifact enters, support X.$Go for the Goal! -- {3},{T}: Until end of turn, target creature gains "Whenever this creature deals combat damage to a player, draw a card for each kind of counter on it" and it can't be blocked this turn.| +Blue Mage's Cane|Final Fantasy Commander|35|R|{2}{U}|Artifact - Equipment|||Job select$Equipped creature gets +0/+2, is a Wizard in addition to its other types, and has "Whenever this creature attacks, exile up to one target instant or sorcery card from defending player's graveyard. If you do, copy it. You may cast the copy by paying {3} rather than paying its mana cost."$Spirit of the Whalaqee -- Equip {2}| +Hermes, Overseer of Elpis|Final Fantasy Commander|36|R|{3}{U}|Legendary Creature - Elder Wizard|2|4|Whenever you cast a noncreature spell, create a 1/1 blue Bird creature token with flying and vigilance.$Whenever you attack with one or more Birds, scry 2.| +Hraesvelgr of the First Brood|Final Fantasy Commander|37|R|{4}{U}|Legendary Creature - Elder Dragon|5|5|Flying, vigilance, ward {2}$Shiva's Aid -- When Hraesvelgr enters and whenever you cast a noncreature spell, target creature gets +1/+0 until end of turn and can't be blocked this turn.| +Lulu, Stern Guardian|Final Fantasy Commander|38|R|{2}{U}|Legendary Creature - Human Wizard|2|3|Whenever an opponent attacks you, choose target creature attacking you. Put a stun counter on that creature.${3}{U}: Proliferate.| +O'aka, Traveling Merchant|Final Fantasy Commander|39|R|{1}{U}|Legendary Creature - Human Citizen|1|2|{T}, Remove a counter from a nonland permanent you control: Draw a card.| +Observed Stasis|Final Fantasy Commander|40|R|{3}{U}|Enchantment - Aura|||Flash$Enchant creature an opponent controls$When this Aura enters, remove enchanted creature from combat. Then draw a card for each tapped creature its controller controls.$Enchanted creature loses all abilities and can't attack or block.| +Rikku, Resourceful Guardian|Final Fantasy Commander|41|R|{2}{U}|Legendary Creature - Human Artificer|2|3|Whenever you put one or more counters on a creature, until end of turn, that creature can't be blocked by creatures your opponents control.$Steal -- {1},{T}: Move a counter from target creature an opponent controls onto target creature you control. Activate only as a sorcery.| +Summon: Valefor|Final Fantasy Commander|42|R|{4}{U}|Enchantment Creature - Saga Drake|5|4|(As this Saga enters and after your draw step, add a lore counter. Sacrifice after IV.)$I - Sonic Wings -- Each opponent chooses a creature with the greatest mana value among creatures they control. Return those creatures to their owners' hands.$II, III, IV - Tap up to one target creature and put a stun counter on it.$Flying| +Espers to Magicite|Final Fantasy Commander|43|R|{3}{B}|Instant|||Exile each opponent's graveyard. When you do, choose up to one target creature card exiled this way. Create a token that's a copy of that card, except it's an artifact and it loses all other card types.| +Eye of Nidhogg|Final Fantasy Commander|44|R|{2}{B}|Legendary Enchantment - Aura|||Enchant creature$Enchanted creature is a black Dragon with base power and toughness 4/2, has flying and deathtouch, and is goaded.$When Eye of Nidhogg is put into a graveyard from the battlefield, return it to its owner's hand.| +The Falcon, Airship Restored|Final Fantasy Commander|45|R|{2}{B}|Legendary Artifact - Vehicle|4|3|Flying$Whenever The Falcon deals combat damage to a player, you may sacrifice it. When you do, return target creature card from your graveyard to the battlefield.${4}{B}: Return this card from your graveyard to the battlefield tapped.$Crew 2| +Fandaniel, Telophoroi Ascian|Final Fantasy Commander|46|R|{4}{B}|Legendary Creature - Elder Wizard|4|5|Whenever you cast an instant or sorcery spell, surveil 1.$At the beginning of your end step, each opponent may sacrifice a nontoken creature of their choice. Each opponent who doesn't loses 2 life for each instant and sorcery card in your graveyard.| +Interceptor, Shadow's Hound|Final Fantasy Commander|47|R|{2}{B}{B}|Legendary Creature - Dog|4|3|Menace$Assassins you control have menace.$Whenever you attack with one or more legendary creatures, you may pay {2}{B}. If you do, return this card from your graveyard to the battlefield tapped and attacking.| +Reaper's Scythe|Final Fantasy Commander|48|R|{2}{B}|Artifact - Equipment|||Job select$At the beginning of your end step, put a soul counter on this Equipment for each player who lost life this turn.$Equipped creature gets +1/+1 for each soul counter on this Equipment and is an Assassin in addition to its other types.$Death Sickle -- Equip {2}| +Rejoin the Fight|Final Fantasy Commander|49|R|{5}{B}|Sorcery|||Mill three cards. Then starting with the next opponent in turn order, each opponent chooses a creature card in your graveyard that hasn't been chosen. Return each card chosen this way to the battlefield under your control.| +Shadow, Mysterious Assassin|Final Fantasy Commander|50|R|{2}{B}|Legendary Creature - Human Assassin|3|3|Deathtouch$Throw -- Whenever Shadow deals combat damage to a player, you may sacrifice another nonland permanent. If you do, draw two cards and each opponent loses life equal to the mana value of the sacrificed permanent.| +Siegfried, Famed Swordsman|Final Fantasy Commander|51|R|{3}{B}|Legendary Creature - Human Warrior Rogue|2|2|Menace$When Siegfried enters, mill three cards. Then put X +1/+1 counters on Siegfried, where X is twice the number of creature cards in your graveyard.| +Transpose|Final Fantasy Commander|52|R|{2}{B}|Instant|||Draw a card, then discard a card. You lose 1 life. If this spell was cast from your hand, create a 0/1 black Wizard creature token with "Whenever you cast a noncreature spell, this token deals 1 damage to each opponent."$Rebound| +Avalanche of Sector 7|Final Fantasy Commander|53|R|{2}{R}|Legendary Creature - Human Rebel|*|3|Menace$Avalanche of Sector 7's power is equal to the number of artifacts your opponents control.$Whenever an opponent activates an ability of an artifact they control, Avalanche of Sector 7 deals 1 damage to that player.| +Cait Sith, Fortune Teller|Final Fantasy Commander|54|R|{3}{R}|Legendary Artifact Creature - Cat Moogle|3|3|Lucky Slots -- At the beginning of combat on your turn, scry 1, then exile the top card of your library. You may play that card this turn. When you exile a card this way, target creature you control gets +X/+0 until end of turn, where X is that card's mana value.| +Gau, Feral Youth|Final Fantasy Commander|55|R|{1}{R}|Legendary Creature - Human Berserker|2|2|Rage -- Whenever Gau attacks, put a +1/+1 counter on it.$At the beginning of each end step, if a card left your graveyard this turn, Gau deals damage equal to its power to each opponent.| +Gogo, Mysterious Mime|Final Fantasy Commander|56|R|{3}{R}|Legendary Creature - Wizard|2|2|At the beginning of combat on your turn, you may have Gogo become a copy of another target creature you control until end of turn, except its name is Gogo, Mysterious Mime. If you do, Gogo and that creature each get +2/+0 and gain haste until end of turn and attack this turn if able.| +Sabin, Master Monk|Final Fantasy Commander|57|R|{4}{R}|Legendary Creature Human Noble Monk|4|3|Double strike$Blitz--{2}{R}{R}, Discard a card.$You may cast this card from your graveyard using its blitz ability.| +Snort|Final Fantasy Commander|58|R|{3}{R}|Sorcery|||Each player may discard their hand and draw five cards. Then Snort deals 5 damage to each opponent who discarded their hand this way.$Flashback {5}{R}| +Strago and Relm|Final Fantasy Commander|59|R|{2}{R}|Legendary Creature - Human Wizard|1|3|Sketch and Lore--{2}{R},{T}: Target opponent exiles cards from the top of their library until they exile an instant, sorcery, or creature card. You may cast that card without paying its mana cost. If you cast a creature spell this way, it gains haste and "At the beginning of the end step, sacrifice this creature." Activate only as a sorcery.| +Summon: Esper Valigarmanda|Final Fantasy Commander|60|R|{3}{R}|Enchantment Creature - Saga Drake|3|3|(As this Saga enters and after your draw step, add a lore counter. Sacrifice after IV.)$I -- Exile an instant or sorcery card from each graveyard.$II, III, IV -- Add {R} for each lore counter on this Saga. You may cast an instant or sorcery card exiled with this Saga, and mana of any type can be spent to cast that spell.$Flying, haste| +Summon: Kujata|Final Fantasy Commander|61|R|{5}{R}|Enchantment Creature - Saga Ox|7|5|(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I -- Lightning -- This creature deals 3 damage to each of up to two target creatures.$II -- Ice -- Up to three target creatures can't block this turn.$III -- Fire -- Discard a card, then draw two cards. When you discard a card this way, this creature deals damage equal to that card's mana value to each opponent.$Trample, haste| +Ultimate Magic: Meteor|Final Fantasy Commander|62|R|{5}{R}|Sorcery|||Ultimate Magic: Meteor deals 7 damage to each creature. If this spell was cast from exile, for each opponent, choose an artifact or land that player controls. Destroy the chosen permanents.$Foretell {5}{R}| +Umaro, Raging Yeti|Final Fantasy Commander|63|R|{5}{R}|Legendary Creature - Yeti Berserker|6|6|Trample$At the beginning of combat on your turn, choose one at random --$* Other creatures you control get +3/+0 and gain trample until end of turn.$* Discard your hand, then draw four cards.$* Umaro deals 5 damage to any target.| +Vincent, Vengeful Atoner|Final Fantasy Commander|64|R|{2}{R}|Legendary Creature - Assassin|3|3|Menace$Whenever one or more creatures you control deal combat damage to a player, put a +1/+1 counter on Vincent.$Chaos -- Whenever Vincent deals combat damage to an opponent, it deals that much damage to each other opponent if Vincent's power is 7 or greater.| +Yuffie, Materia Hunter|Final Fantasy Commander|65|R|{2}{R}|Legendary Creature - Human Ninja|3|3|Ninjutsu {1}{R}$When Yuffie enters, gain control of target noncreature artifact for as long as you control Yuffie. Then you may attach an Equipment you control to Yuffie.| +Bugenhagen, Wise Elder|Final Fantasy Commander|66|R|{1}{G}|Legendary Creature - Human Shaman|1|3|Reach$At the beginning of your upkeep, if you control a creature with power 7 or greater, draw a card.${T}: Add one mana of any color.| +Lifestream's Blessing|Final Fantasy Commander|67|R|{4}{G}{G}|Instant|||Draw X cards, where X is the greatest power among creatures you controlled as you cast this spell. If this spell was cast from exile, you gain twice X life.$Foretell {4}{G}| +Maester Seymour|Final Fantasy Commander|68|R|{2}{G}|Legendary Creature - Human Elf Cleric|1|3|At the beginning of combat on your turn, put a number of +1/+1 counters equal to Maester Seymour's power on another target creature you control.${3}{G}{G}: Monstrosity X, where X is the number of counters among creatures you control.| +Professor Hojo|Final Fantasy Commander|69|R|{1}{G}|Legendary Creature - Human Scientist|2|2|The first activated ability you activate during your turn that targets a creature you control costs {2} less to activate.$Whenever one or more creatures you control become the target of an activated ability, draw a card. This ability triggers only once each turn.| +Sphere Grid|Final Fantasy Commander|70|R|{1}{G}|Enchantment|||Whenever a creature you control deals combat damage to a player, put a +1/+1 counter on that creature.$Unlock Ability -- Creatures you control with +1/+1 counters on them have reach and trample.| +Summon: Magus Sisters|Final Fantasy Commander|71|R|{4}{G}|Enchantment Creature - Saga Faerie|5|5|(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I,II,III - Choose one at random --$* Combine Powers! -- Put three +1/+1 counters on target creature.$* Defense! -- Put a shield counter on target creature. You gain 3 life.$* Fight! -- This creature fights up to one target creature an opponent controls.$Haste| +Summoning Materia|Final Fantasy Commander|72|R|{2}{G}|Artifact - Equipment|||You may look at the top card of your library any time.$As long as this Equipment is attached to a creature, you may cast creature spells from the top of your library.$Equipped creature gets +2/+2 and has vigilance and "{T}: Add {G}."$Equip {2}| +Tromell, Seymour's Butler|Final Fantasy Commander|73|R|{2}{G}|Legendary Creature - Elf Advisor|2|3|Each other nontoken creature you control enters with an additional +1/+1 counter on it.${1},{T}: Proliferate X times, where X is the number of nontoken creatures you control that entered this turn.| +Yuna's Decision|Final Fantasy Commander|74|R|{3}{G}|Sorcery|||Choose one --$* Continue the Pilgrimage -- Sacrifice a creature. If you do, draw a card, then you may put a creature card and/or a land card from your hand onto the battlefield.$* Find Another Way -- Return one or two target permanent cards from your graveyard to your hand.| +Yuna's Whistle|Final Fantasy Commander|75|R|{1}{G}{G}|Instant|||Reveal cards from the top of your library until you reveal a creature card. Put that card into your hand and the rest on the bottom of your library in a random order. When you reveal a creature card this way, put X+1/+1 counters on target creature you control, where X is the mana value of that card.| +Aerith, Last Ancient|Final Fantasy Commander|76|R|{2}{G}{W}|Legendary Creature - Human Cleric Druid|3|5|Lifelink$Raise -- At the beginning of your end step, if you gained life this turn, return target creature card from your graveyard to your hand. If you gained 7 or more life this turn, return that card to the battlefield instead.| +Ardbert, Warrior of Darkness|Final Fantasy Commander|77|R|{1}{W}{B}|Legendary Creature - Spirit Warrior|2|2|Whenever you cast a white spell, put a +1/+1 counter on each legendary creature you control. They gain vigilance until end of turn.$Whenever you cast a black spell, put a +1/+1 counter on each legendary creature you control. They gain menace until end of turn.| +Banon, the Returners' Leader|Final Fantasy Commander|78|R|{R}{W}|Legendary Creature Human Cleric Rebel|1|3|Pray -- Once during each of your turns, you may cast a creature spell from among cards in your graveyard that were put there from anywhere other than the battlefield this turn.$Whenever you attack, you may pay {1} and discard a card. If you do, draw a card.| +Barret, Avalanche Leader|Final Fantasy Commander|79|R|{2}{R}{G}|Legendary Creature - Human Rebel|4|4|Reach$Avalanche! -- Whenever an Equipment you control enters, create a 2/2 red Rebel creature token.$At the beginning of combat on your turn, attach up to one target Equipment you control to target Rebel you control.| +Edgar, Master Machinist|Final Fantasy Commander|80|R|{2}{R}{W}|Legendary Creature - Human Artificer Noble|2|4|Once during each of your turns, you may cast an artifact spell from your graveyard. If you cast a spell this way, that artifact enters tapped.$Tools -- Whenever Edgar attacks, it gets +X/+0 until end of turn, where X is the greatest mana value among artifacts you control.| +Emet-Selch of the Third Seat|Final Fantasy Commander|81|R|{2}{U}{B}|Legendary Creature - Elder Wizard|3|4|Spells you cast from your graveyard cost {2} less to cast.$Whenever one or more opponents lose life, you may cast target instant or sorcery card from your graveyard. If that spell would be put into your graveyard, exile it instead. Do this only once each turn.| +Estinien Varlineau|Final Fantasy Commander|82|R|{2}{W}{B}|Legendary Creature - Elf Warrior|3|3|Whenever you cast a noncreature spell, put a +1/+1 counter on Estinien Varlineau. It gains flying until end of turn.$At the beginning of your second main phase, you draw X cards and lose X life, where X is the number of your opponents who were dealt combat damage by Estinien Varlineau or a Dragon this turn.| +Hildibrand Manderville|Final Fantasy Commander|83|R|{1}{W}|Legendary Creature - Human Detective|2|2|Creature tokens you control get +1/+1.$When Hildibrand Manderville dies, you may cast it from your graveyard as an Adventure until the end of your next turn.| +Gentleman's Rise|Final Fantasy Commander|83|R|{2}{B}|Instant - Adventure|2|2|Create a 2/2 black Zombie creature token.| +Kefka, Dancing Mad|Final Fantasy Commander|84|R|{5}{B}{R}|Legendary Creature - Human Wizard|6|6|During your turn, Kefka has indestructible.$At the beginning of your end step, exile a card at random from each opponent's graveyard. You may cast any number of spells from among cards exiled this way without paying their mana costs. Then each player who owns a spell you cast this way loses life equal to its mana value.| +Kimahri, Valiant Guardian|Final Fantasy Commander|85|R|{2}{G}{U}|Legendary Creature - Cat Warrior|3|3|Vigilance$Ronso Rage -- At the beginning of combat on your turn, put a +1/+1 counter on Kimahri and tap target creature an opponent controls. Then you may have Kimahri become a copy of that creature, except its name is Kimahri, Valiant Guardian and it has vigilance and this ability.| +Krile Baldesion|Final Fantasy Commander|86|R|{W}{U}|Legendary Creature - Dwarf Wizard|2|1|Lifelink$Trace Aether -- Whenever you cast a noncreature spell, you may return target creature card with mana value equal to that spell's mana value from your graveyard to your hand. Do this only once each turn.| +Locke, Treasure Hunter|Final Fantasy Commander|87|R|{1}{B}{R}|Legendary Creature - Human Rogue|2|3|Locke can't be blocked by creatures with greater power.$Mug -- Whenever Locke attacks, each player mills a card. If a land card was milled this way, create a Treasure token. Until end of turn, you may cast a spell from among those cards.| +Lyse Hext|Final Fantasy Commander|88|R|{1}{W}{U}|Legendary Creature - Human Rebel Monk|2|2|Prowess$Noncreature spells you cast cost {1} less to cast.$As long as you've cast two or more noncreature spells this turn, Lyse Hext has double strike.| +Mog, Moogle Warrior|Final Fantasy Commander|89|R|{1}{R}{W}|Legendary Creature - Moogle Warrior|1|2|Lifelink$Dance -- At the beginning of your end step, each player may discard a card. Each player who discarded a card this way draws a card. If a creature card was discarded this way, you create a 1/2 white Moogle creature token with lifelink. Then if a noncreature card was discarded this way, put a +1/+1 counter on each Moogle you control.| +Papalymo Totolymo|Final Fantasy Commander|90|R|{W}{B}|Legendary Creature - Dwarf Wizard|1|2|Whenever you cast a noncreature spell, Papalymo Totolymo deals 1 damage to each opponent and you gain 1 life.${4},{T}, Sacrifice Papalymo Totolymo: Each opponent who lost life this turn sacrifices a creature with the greatest power among creatures they control.| +Red XIII, Proud Warrior|Final Fantasy Commander|91|R|{1}{R}{G}|Legendary Creature - Beast Warrior|3|3|Vigilance, trample$Other modified creatures you control have vigilance and trample.$Cosmo Memory -- When Red XIII enters, return target Aura or Equipment card from your graveyard to your hand.| +Sephiroth, Fallen Hero|Final Fantasy Commander|92|R|{3}{R}{W}|Legendary Creature - Human Avatar Soldier|7|5|Jenova Cells -- Whenever Sephiroth attacks, you may put a cell counter on target creature. Until end of turn, each modified creature you control has base power and toughness 7/5.$The Reunion -- {3}, Sacrifice a modified creature: Return this card from your graveyard to the battlefield tapped.| +Setzer, Wandering Gambler|Final Fantasy Commander|93|R|{1}{B}{R}|Legendary Creature - Human Rogue Pilot|2|2|When Setzer enters, create The Blackjack, a legendary 3/3 colorless Vehicle artifact token with flying and crew 2.$Whenever a Vehicle you control deals combat damage to a player, flip a coin.$Whenever you win a coin flip, create two tapped Treasure tokens.| +Shelinda, Yevon Acolyte|Final Fantasy Commander|94|R|{G}{W}|Legendary Creature - Human Cleric|2|2|Lifelink$Whenever another creature you control enters, put a +1/+1 counter on that creature if its power is less than Shelinda's power. Otherwise, put a +1/+1 counter on Shelinda.| +Sin, Unending Cataclysm|Final Fantasy Commander|95|R|{5}{G}{U}|Legendary Creature - Leviathan Avatar|5|5|Flying, trample$As Sin enters, remove all counters from any number of artifacts, creatures, and enchantments. Sin enters with X+1/+1 counters on it, where X is twice the number of counters removed this way.$When Sin dies, put its counters on target creature you control, then shuffle this card into its owner's library.| +Urianger Augurelt|Final Fantasy Commander|96|R|{W}{U}|Legendary Creature - Elf Advisor|1|3|Whenever you play a land from exile or cast a spell from exile, you gain 2 life.$Draw Arcanum -- {T}: Look at the top card of your library. You may exile it face down.$Play Arcanum -- {T}: Until end of turn, you may play cards exiled with Urianger Augurelt. Spells you cast this way cost {2} less to cast.| +Wakka, Devoted Guardian|Final Fantasy Commander|97|R|{2}{G}{W}|Legendary Creature - Human Warrior|4|4|Reach, trample$Whenever Wakka deals combat damage to a player, destroy up to one target artifact that player controls and put a +1/+1 counter on Wakka.$Blitzball Captain -- At the beginning of your end step, if a counter was put on Wakka this turn, put a +1/+1 counter on each other creature you control.| +Conformer Shuriken|Final Fantasy Commander|98|R|{2}|Legendary Artifact - Equipment|||Equipped creature has "Whenever this creature attacks, tap target creature defending player controls. If that creature has greater power than this creature, put a number of +1/+1 counters on this creature equal to the difference."$Equip {2}| +The Warring Triad|Final Fantasy Commander|99|R|{3}|Legendary Artifact Creature - God|5|5|Flying, trample, haste$As long as there are fewer than eight cards in your graveyard, The Warring Triad isn't a creature.${T}, Mill a card: Target player adds one mana of any color.| +Wrecking Ball Arm|Final Fantasy Commander|100|R|{2}|Legendary Artifact - Equipment|||Equipped creature has base power and toughness 7/7 and can't be blocked by creatures with power 2 or less.$Equip legendary creature {3}$Equip {7}| +Champions from Beyond|Final Fantasy Commander|101|R|{X}{W}{W}|Enchantment|||When this enchantment enters, create X 1/1 colorless Hero creature tokens.$Light Party -- Whenever you attack with four or more creatures, scry 2, then draw a card.$Full Party -- Whenever you attack with eight or more creatures, those creatures get +4/+4 until end of turn.| +Chocobo Knights|Final Fantasy Commander|102|R|{3}{W}|Creature - Human Knight|3|3|Whenever you attack, creatures you control with counters on them gain double strike until end of turn.| +Cloud's Limit Break|Final Fantasy Commander|103|R|{1}{W}|Instant|||Tiered$* Cross-Slash -- {0} -- Destroy target tapped creature.$* Blade Beam -- {1} -- Destroy any number of target tapped creatures with different controllers.$* Omnislash -- {3}{W} -- Destroy all tapped creatures.| +Coin of Fate|Final Fantasy Commander|104|R|{1}{W}|Artifact|||When this artifact enters, surveil 1.${3}{W},{T}, Exile two creature cards from your graveyard, Sacrifice this artifact: An opponent chooses one of the exiled cards. You put that card on the bottom of your library and return the other to the battlefield tapped. You become the monarch.| +Dancer's Chakrams|Final Fantasy Commander|105|R|{3}{W}|Artifact - Equipment|||Job select$Equipped creature gets +2/+2, has lifelink and "Other commanders you control get +2/+2 and have lifelink," and is a Performer in addition to its other types.$Krishna -- Equip {3}| +Helitrooper|Final Fantasy Commander|106|R|{1}{W}|Creature - Human Soldier|1|2|Flying$Whenever this creature attacks, another target attacking creature gains flying until end of turn.$Equip abilities you activate that target this creature cost {2} less to activate.| +Protection Magic|Final Fantasy Commander|107|R|{1}{W}|Instant|||Put a shield counter on each of up to three target creatures.| +SOLDIER Military Program|Final Fantasy Commander|108|R|{2}{W}|Enchantment|||At the beginning of combat on your turn, choose one. If you control a commander, you may choose both instead.$* Create a 1/1 white Soldier creature token.$* Put a +1/+1 counter on each of up to two Soldiers you control.| +Summoner's Sending|Final Fantasy Commander|109|R|{1}{W}|Enchantment|||At the beginning of your end step, you may exile target creature card from a graveyard. If you do, create a 1/1 white Spirit creature token with flying. Put a +1/+1 counter on it if the exiled card's mana value is 4 or greater.| +Ultimate Magic: Holy|Final Fantasy Commander|110|R|{2}{W}|Instant|||Permanents you control gain indestructible until end of turn. If this spell was cast from exile, prevent all damage that would be dealt to you this turn.$Foretell {2}{W}| +Blitzball Stadium|Final Fantasy Commander|111|R|{X}{U}|Artifact|||When this artifact enters, support X.$Go for the Goal! -- {3},{T}: Until end of turn, target creature gains "Whenever this creature deals combat damage to a player, draw a card for each kind of counter on it" and it can't be blocked this turn.| +Blue Mage's Cane|Final Fantasy Commander|112|R|{2}{U}|Artifact - Equipment|||Job select$Equipped creature gets +0/+2, is a Wizard in addition to its other types, and has "Whenever this creature attacks, exile up to one target instant or sorcery card from defending player's graveyard. If you do, copy it. You may cast the copy by paying {3} rather than paying its mana cost."$Spirit of the Whalaqee -- Equip {2}| +Observed Stasis|Final Fantasy Commander|113|R|{3}{U}|Enchantment - Aura|||Flash$Enchant creature an opponent controls$When this Aura enters, remove enchanted creature from combat. Then draw a card for each tapped creature its controller controls.$Enchanted creature loses all abilities and can't attack or block.| +Espers to Magicite|Final Fantasy Commander|114|R|{3}{B}|Instant|||Exile each opponent's graveyard. When you do, choose up to one target creature card exiled this way. Create a token that's a copy of that card, except it's an artifact and it loses all other card types.| +Eye of Nidhogg|Final Fantasy Commander|115|R|{2}{B}|Legendary Enchantment - Aura|||Enchant creature$Enchanted creature is a black Dragon with base power and toughness 4/2, has flying and deathtouch, and is goaded.$When Eye of Nidhogg is put into a graveyard from the battlefield, return it to its owner's hand.| +The Falcon, Airship Restored|Final Fantasy Commander|116|R|{2}{B}|Legendary Artifact - Vehicle|4|3|Flying$Whenever The Falcon deals combat damage to a player, you may sacrifice it. When you do, return target creature card from your graveyard to the battlefield.${4}{B}: Return this card from your graveyard to the battlefield tapped.$Crew 2| +Reaper's Scythe|Final Fantasy Commander|117|R|{2}{B}|Artifact - Equipment|||Job select$At the beginning of your end step, put a soul counter on this Equipment for each player who lost life this turn.$Equipped creature gets +1/+1 for each soul counter on this Equipment and is an Assassin in addition to its other types.$Death Sickle -- Equip {2}| +Rejoin the Fight|Final Fantasy Commander|118|R|{5}{B}|Sorcery|||Mill three cards. Then starting with the next opponent in turn order, each opponent chooses a creature card in your graveyard that hasn't been chosen. Return each card chosen this way to the battlefield under your control.| +Transpose|Final Fantasy Commander|119|R|{2}{B}|Instant|||Draw a card, then discard a card. You lose 1 life. If this spell was cast from your hand, create a 0/1 black Wizard creature token with "Whenever you cast a noncreature spell, this token deals 1 damage to each opponent."$Rebound| +Snort|Final Fantasy Commander|120|R|{3}{R}|Sorcery|||Each player may discard their hand and draw five cards. Then Snort deals 5 damage to each opponent who discarded their hand this way.$Flashback {5}{R}| +Ultimate Magic: Meteor|Final Fantasy Commander|121|R|{5}{R}|Sorcery|||Ultimate Magic: Meteor deals 7 damage to each creature. If this spell was cast from exile, for each opponent, choose an artifact or land that player controls. Destroy the chosen permanents.$Foretell {5}{R}| +Lifestream's Blessing|Final Fantasy Commander|122|R|{4}{G}{G}|Instant|||Draw X cards, where X is the greatest power among creatures you controlled as you cast this spell. If this spell was cast from exile, you gain twice X life.$Foretell {4}{G}| +Sphere Grid|Final Fantasy Commander|123|R|{1}{G}|Enchantment|||Whenever a creature you control deals combat damage to a player, put a +1/+1 counter on that creature.$Unlock Ability -- Creatures you control with +1/+1 counters on them have reach and trample.| +Summoning Materia|Final Fantasy Commander|124|R|{2}{G}|Artifact - Equipment|||You may look at the top card of your library any time.$As long as this Equipment is attached to a creature, you may cast creature spells from the top of your library.$Equipped creature gets +2/+2 and has vigilance and "{T}: Add {G}."$Equip {2}| +Yuna's Decision|Final Fantasy Commander|125|R|{3}{G}|Sorcery|||Choose one --$* Continue the Pilgrimage -- Sacrifice a creature. If you do, draw a card, then you may put a creature card and/or a land card from your hand onto the battlefield.$* Find Another Way -- Return one or two target permanent cards from your graveyard to your hand.| +Yuna's Whistle|Final Fantasy Commander|126|R|{1}{G}{G}|Instant|||Reveal cards from the top of your library until you reveal a creature card. Put that card into your hand and the rest on the bottom of your library in a random order. When you reveal a creature card this way, put X+1/+1 counters on target creature you control, where X is the mana value of that card.| +Conformer Shuriken|Final Fantasy Commander|127|R|{2}|Legendary Artifact - Equipment|||Equipped creature has "Whenever this creature attacks, tap target creature defending player controls. If that creature has greater power than this creature, put a number of +1/+1 counters on this creature equal to the difference."$Equip {2}| +Wrecking Ball Arm|Final Fantasy Commander|128|R|{2}|Legendary Artifact - Equipment|||Equipped creature has base power and toughness 7/7 and can't be blocked by creatures with power 2 or less.$Equip legendary creature {3}$Equip {7}| +Alisaie Leveilleur|Final Fantasy Commander|129|R|{2}{W}|Legendary Creature - Elf Wizard|3|2|Partner with Alphinaud Leveilleur$First strike$Dualcast -- The second spell you cast each turn costs {2} less to cast.| +Auron, Venerated Guardian|Final Fantasy Commander|130|R|{3}{W}|Legendary Creature - Human Spirit Samurai|2|5|Vigilance$Shooting Star -- Whenever Auron attacks, put a +1/+1 counter on it. When you do, exile target creature defending player controls with power less than Auron's power until Auron leaves the battlefield.| +Cid, Freeflier Pilot|Final Fantasy Commander|131|R|{1}{W}|Legendary Creature - Human Warrior Pilot|2|2|Equipment and Vehicle spells you cast cost {1} less to cast.$Jump -- During your turn, Cid has flying.${2},{T}: Return target Equipment or Vehicle card from your graveyard to your hand.| +Cyan, Vengeful Samurai|Final Fantasy Commander|132|R|{6}{W}|Legendary Creature - Human Samurai|3|3|This spell costs {1} less to cast for each creature card in your graveyard.$Double strike$Whenever one or more creature cards leave your graveyard, put a +1/+1 counter on Cyan.| +Elena, Turk Recruit|Final Fantasy Commander|133|R|{2}{W}|Legendary Creature - Human Assassin|1|4|When Elena enters, return target non-Assassin historic card from your graveyard to your hand.$Whenever you cast a historic spell, put a +1/+1 counter on Elena.| +Gatta and Luzzu|Final Fantasy Commander|134|R|{2}{W}|Legendary Creature - Human Soldier|1|1|Flash$When Gatta and Luzzu enters, choose target creature you control. If damage would be dealt to that creature this turn, prevent that damage and put that many +1/+1 counters on it.| +General Leo Cristophe|Final Fantasy Commander|135|R|{4}{W}|Legendary Creature - Human Soldier|2|2|When General Leo Cristophe enters, return up to one target creature card with mana value 3 or less from your graveyard to the battlefield. Then put a +1/+1 counter on General Leo Cristophe for each creature you control.| +Heidegger, Shinra Executive|Final Fantasy Commander|136|R|{3}{W}|Legendary Creature - Human Soldier|3|3|At the beginning of combat on your turn, target creature you control gets +X/+0 until end of turn, where X is the number of Soldiers you control.$At the beginning of your end step, create a number of 1/1 white Soldier creature tokens equal to the number of opponents who control more creatures than you.| +Lord Jyscal Guado|Final Fantasy Commander|137|R|{1}{W}|Legendary Creature - Spirit Cleric|2|1|Flying$At the beginning of each end step, if you put a counter on a creature this turn, investigate.| +Tataru Taru|Final Fantasy Commander|138|R|{1}{W}|Legendary Creature - Dwarf Advisor|0|3|When Tataru Taru enters, you draw a card and target opponent may draw a card.$Scions' Secretary -- Whenever an opponent draws a card, if it isn't that player's turn, create a tapped Treasure token. This ability triggers only once each turn.| +Thancred Waters|Final Fantasy Commander|139|R|{4}{W}|Legendary Creature - Human Warrior|3|5|Flash$Royal Guard -- When Thancred Waters enters, another target legendary permanent you control gains indestructible for as long as you control Thancred Waters.$Whenever you cast a noncreature spell, Thancred Waters gains indestructible until end of turn.| +Alphinaud Leveilleur|Final Fantasy Commander|140|R|{3}{U}|Legendary Creature - Elf Wizard|2|4|Partner with Alisaie Leveilleur$Vigilance$Eukrasia -- Whenever you cast your second spell each turn, draw a card.| +Hermes, Overseer of Elpis|Final Fantasy Commander|141|R|{3}{U}|Legendary Creature - Elder Wizard|2|4|Whenever you cast a noncreature spell, create a 1/1 blue Bird creature token with flying and vigilance.$Whenever you attack with one or more Birds, scry 2.| +Hraesvelgr of the First Brood|Final Fantasy Commander|142|R|{4}{U}|Legendary Creature - Elder Dragon|5|5|Flying, vigilance, ward {2}$Shiva's Aid -- When Hraesvelgr enters and whenever you cast a noncreature spell, target creature gets +1/+0 until end of turn and can't be blocked this turn.| +Lulu, Stern Guardian|Final Fantasy Commander|143|R|{2}{U}|Legendary Creature - Human Wizard|2|3|Whenever an opponent attacks you, choose target creature attacking you. Put a stun counter on that creature.${3}{U}: Proliferate.| +O'aka, Traveling Merchant|Final Fantasy Commander|144|R|{1}{U}|Legendary Creature - Human Citizen|1|2|{T}, Remove a counter from a nonland permanent you control: Draw a card.| +Rikku, Resourceful Guardian|Final Fantasy Commander|145|R|{2}{U}|Legendary Creature - Human Artificer|2|3|Whenever you put one or more counters on a creature, until end of turn, that creature can't be blocked by creatures your opponents control.$Steal -- {1},{T}: Move a counter from target creature an opponent controls onto target creature you control. Activate only as a sorcery.| +Fandaniel, Telophoroi Ascian|Final Fantasy Commander|146|R|{4}{B}|Legendary Creature - Elder Wizard|4|5|Whenever you cast an instant or sorcery spell, surveil 1.$At the beginning of your end step, each opponent may sacrifice a nontoken creature of their choice. Each opponent who doesn't loses 2 life for each instant and sorcery card in your graveyard.| +Interceptor, Shadow's Hound|Final Fantasy Commander|147|R|{2}{B}{B}|Legendary Creature - Dog|4|3|Menace$Assassins you control have menace.$Whenever you attack with one or more legendary creatures, you may pay {2}{B}. If you do, return this card from your graveyard to the battlefield tapped and attacking.| +Shadow, Mysterious Assassin|Final Fantasy Commander|148|R|{2}{B}|Legendary Creature - Human Assassin|3|3|Deathtouch$Throw -- Whenever Shadow deals combat damage to a player, you may sacrifice another nonland permanent. If you do, draw two cards and each opponent loses life equal to the mana value of the sacrificed permanent.| +Siegfried, Famed Swordsman|Final Fantasy Commander|149|R|{3}{B}|Legendary Creature - Human Warrior Rogue|2|2|Menace$When Siegfried enters, mill three cards. Then put X +1/+1 counters on Siegfried, where X is twice the number of creature cards in your graveyard.| +Avalanche of Sector 7|Final Fantasy Commander|150|R|{2}{R}|Legendary Creature - Human Rebel|*|3|Menace$Avalanche of Sector 7's power is equal to the number of artifacts your opponents control.$Whenever an opponent activates an ability of an artifact they control, Avalanche of Sector 7 deals 1 damage to that player.| +Cait Sith, Fortune Teller|Final Fantasy Commander|151|R|{3}{R}|Legendary Artifact Creature - Cat Moogle|3|3|Lucky Slots -- At the beginning of combat on your turn, scry 1, then exile the top card of your library. You may play that card this turn. When you exile a card this way, target creature you control gets +X/+0 until end of turn, where X is that card's mana value.| +Gau, Feral Youth|Final Fantasy Commander|152|R|{1}{R}|Legendary Creature - Human Berserker|2|2|Rage -- Whenever Gau attacks, put a +1/+1 counter on it.$At the beginning of each end step, if a card left your graveyard this turn, Gau deals damage equal to its power to each opponent.| +Gogo, Mysterious Mime|Final Fantasy Commander|153|R|{3}{R}|Legendary Creature - Wizard|2|2|At the beginning of combat on your turn, you may have Gogo become a copy of another target creature you control until end of turn, except its name is Gogo, Mysterious Mime. If you do, Gogo and that creature each get +2/+0 and gain haste until end of turn and attack this turn if able.| +Sabin, Master Monk|Final Fantasy Commander|154|R|{4}{R}|Legendary Creature Human Noble Monk|4|3|Double strike$Blitz--{2}{R}{R}, Discard a card.$You may cast this card from your graveyard using its blitz ability.| +Strago and Relm|Final Fantasy Commander|155|R|{2}{R}|Legendary Creature - Human Wizard|1|3|Sketch and Lore--{2}{R},{T}: Target opponent exiles cards from the top of their library until they exile an instant, sorcery, or creature card. You may cast that card without paying its mana cost. If you cast a creature spell this way, it gains haste and "At the beginning of the end step, sacrifice this creature." Activate only as a sorcery.| +Umaro, Raging Yeti|Final Fantasy Commander|156|R|{5}{R}|Legendary Creature - Yeti Berserker|6|6|Trample$At the beginning of combat on your turn, choose one at random --$* Other creatures you control get +3/+0 and gain trample until end of turn.$* Discard your hand, then draw four cards.$* Umaro deals 5 damage to any target.| +Vincent, Vengeful Atoner|Final Fantasy Commander|157|R|{2}{R}|Legendary Creature - Assassin|3|3|Menace$Whenever one or more creatures you control deal combat damage to a player, put a +1/+1 counter on Vincent.$Chaos -- Whenever Vincent deals combat damage to an opponent, it deals that much damage to each other opponent if Vincent's power is 7 or greater.| +Yuffie, Materia Hunter|Final Fantasy Commander|158|R|{2}{R}|Legendary Creature - Human Ninja|3|3|Ninjutsu {1}{R}$When Yuffie enters, gain control of target noncreature artifact for as long as you control Yuffie. Then you may attach an Equipment you control to Yuffie.| +Bugenhagen, Wise Elder|Final Fantasy Commander|159|R|{1}{G}|Legendary Creature - Human Shaman|1|3|Reach$At the beginning of your upkeep, if you control a creature with power 7 or greater, draw a card.${T}: Add one mana of any color.| +Maester Seymour|Final Fantasy Commander|160|R|{2}{G}|Legendary Creature - Human Elf Cleric|1|3|At the beginning of combat on your turn, put a number of +1/+1 counters equal to Maester Seymour's power on another target creature you control.${3}{G}{G}: Monstrosity X, where X is the number of counters among creatures you control.| +Professor Hojo|Final Fantasy Commander|161|R|{1}{G}|Legendary Creature - Human Scientist|2|2|The first activated ability you activate during your turn that targets a creature you control costs {2} less to activate.$Whenever one or more creatures you control become the target of an activated ability, draw a card. This ability triggers only once each turn.| +Tromell, Seymour's Butler|Final Fantasy Commander|162|R|{2}{G}|Legendary Creature - Elf Advisor|2|3|Each other nontoken creature you control enters with an additional +1/+1 counter on it.${1},{T}: Proliferate X times, where X is the number of nontoken creatures you control that entered this turn.| +Aerith, Last Ancient|Final Fantasy Commander|163|R|{2}{G}{W}|Legendary Creature - Human Cleric Druid|3|5|Lifelink$Raise -- At the beginning of your end step, if you gained life this turn, return target creature card from your graveyard to your hand. If you gained 7 or more life this turn, return that card to the battlefield instead.| +Ardbert, Warrior of Darkness|Final Fantasy Commander|164|R|{1}{W}{B}|Legendary Creature - Spirit Warrior|2|2|Whenever you cast a white spell, put a +1/+1 counter on each legendary creature you control. They gain vigilance until end of turn.$Whenever you cast a black spell, put a +1/+1 counter on each legendary creature you control. They gain menace until end of turn.| +Banon, the Returners' Leader|Final Fantasy Commander|165|R|{R}{W}|Legendary Creature Human Cleric Rebel|1|3|Pray -- Once during each of your turns, you may cast a creature spell from among cards in your graveyard that were put there from anywhere other than the battlefield this turn.$Whenever you attack, you may pay {1} and discard a card. If you do, draw a card.| +Barret, Avalanche Leader|Final Fantasy Commander|166|R|{2}{R}{G}|Legendary Creature - Human Rebel|4|4|Reach$Avalanche! -- Whenever an Equipment you control enters, create a 2/2 red Rebel creature token.$At the beginning of combat on your turn, attach up to one target Equipment you control to target Rebel you control.| +Celes, Rune Knight|Final Fantasy Commander|167|M|{1}{R}{W}{B}|Legendary Creature - Human Wizard Knight|4|4|When Celes enters, discard any number of cards, then draw that many cards plus one.$Whenever one or more other creatures you control enter, if one or more of them entered from a graveyard or was cast from a graveyard, put a +1/+1 counter on each creature you control.| +Cloud, Ex-SOLDIER|Final Fantasy Commander|168|M|{2}{R}{G}{W}|Legendary Creature - Human Soldier Mercenary|4|4|Haste$When Cloud enters, attach up to one target Equipment you control to it.$Whenever Cloud attacks, draw a card for each equipped attacking creature you control. Then if Cloud has power 7 or greater, create two Treasure tokens.| +Edgar, Master Machinist|Final Fantasy Commander|169|R|{2}{R}{W}|Legendary Creature - Human Artificer Noble|2|4|Once during each of your turns, you may cast an artifact spell from your graveyard. If you cast a spell this way, that artifact enters tapped.$Tools -- Whenever Edgar attacks, it gets +X/+0 until end of turn, where X is the greatest mana value among artifacts you control.| +Emet-Selch of the Third Seat|Final Fantasy Commander|170|R|{2}{U}{B}|Legendary Creature - Elder Wizard|3|4|Spells you cast from your graveyard cost {2} less to cast.$Whenever one or more opponents lose life, you may cast target instant or sorcery card from your graveyard. If that spell would be put into your graveyard, exile it instead. Do this only once each turn.| +Estinien Varlineau|Final Fantasy Commander|171|R|{2}{W}{B}|Legendary Creature - Elf Warrior|3|3|Whenever you cast a noncreature spell, put a +1/+1 counter on Estinien Varlineau. It gains flying until end of turn.$At the beginning of your second main phase, you draw X cards and lose X life, where X is the number of your opponents who were dealt combat damage by Estinien Varlineau or a Dragon this turn.| +G'raha Tia, Scion Reborn|Final Fantasy Commander|172|M|{W}{U}{B}|Legendary Creature - Cat Wizard|2|3|Lifelink$Throw Wide the Gates -- Whenever you cast a noncreature spell, you may pay X life, where X is that spell's mana value. If you do, create a 1/1 colorless Hero creature token and put X+1/+1 counters on it. Do this only once each turn.| +Hildibrand Manderville|Final Fantasy Commander|173|R|{1}{W}|Legendary Creature - Human Detective|2|2|Creature tokens you control get +1/+1.$When Hildibrand Manderville dies, you may cast it from your graveyard as an Adventure until the end of your next turn.| +Gentleman's Rise|Final Fantasy Commander|173|R|{2}{B}|Instant - Adventure|2|2|Create a 2/2 black Zombie creature token.| +Kefka, Dancing Mad|Final Fantasy Commander|174|R|{5}{B}{R}|Legendary Creature - Human Wizard|6|6|During your turn, Kefka has indestructible.$At the beginning of your end step, exile a card at random from each opponent's graveyard. You may cast any number of spells from among cards exiled this way without paying their mana costs. Then each player who owns a spell you cast this way loses life equal to its mana value.| +Kimahri, Valiant Guardian|Final Fantasy Commander|175|R|{2}{G}{U}|Legendary Creature - Cat Warrior|3|3|Vigilance$Ronso Rage -- At the beginning of combat on your turn, put a +1/+1 counter on Kimahri and tap target creature an opponent controls. Then you may have Kimahri become a copy of that creature, except its name is Kimahri, Valiant Guardian and it has vigilance and this ability.| +Krile Baldesion|Final Fantasy Commander|176|R|{W}{U}|Legendary Creature - Dwarf Wizard|2|1|Lifelink$Trace Aether -- Whenever you cast a noncreature spell, you may return target creature card with mana value equal to that spell's mana value from your graveyard to your hand. Do this only once each turn.| +Locke, Treasure Hunter|Final Fantasy Commander|177|R|{1}{B}{R}|Legendary Creature - Human Rogue|2|3|Locke can't be blocked by creatures with greater power.$Mug -- Whenever Locke attacks, each player mills a card. If a land card was milled this way, create a Treasure token. Until end of turn, you may cast a spell from among those cards.| +Lyse Hext|Final Fantasy Commander|178|R|{1}{W}{U}|Legendary Creature - Human Rebel Monk|2|2|Prowess$Noncreature spells you cast cost {1} less to cast.$As long as you've cast two or more noncreature spells this turn, Lyse Hext has double strike.| +Mog, Moogle Warrior|Final Fantasy Commander|179|R|{1}{R}{W}|Legendary Creature - Moogle Warrior|1|2|Lifelink$Dance -- At the beginning of your end step, each player may discard a card. Each player who discarded a card this way draws a card. If a creature card was discarded this way, you create a 1/2 white Moogle creature token with lifelink. Then if a noncreature card was discarded this way, put a +1/+1 counter on each Moogle you control.| +Papalymo Totolymo|Final Fantasy Commander|180|R|{W}{B}|Legendary Creature - Dwarf Wizard|1|2|Whenever you cast a noncreature spell, Papalymo Totolymo deals 1 damage to each opponent and you gain 1 life.${4},{T}, Sacrifice Papalymo Totolymo: Each opponent who lost life this turn sacrifices a creature with the greatest power among creatures they control.| +Red XIII, Proud Warrior|Final Fantasy Commander|181|R|{1}{R}{G}|Legendary Creature - Beast Warrior|3|3|Vigilance, trample$Other modified creatures you control have vigilance and trample.$Cosmo Memory -- When Red XIII enters, return target Aura or Equipment card from your graveyard to your hand.| +Sephiroth, Fallen Hero|Final Fantasy Commander|182|R|{3}{R}{W}|Legendary Creature - Human Avatar Soldier|7|5|Jenova Cells -- Whenever Sephiroth attacks, you may put a cell counter on target creature. Until end of turn, each modified creature you control has base power and toughness 7/5.$The Reunion -- {3}, Sacrifice a modified creature: Return this card from your graveyard to the battlefield tapped.| +Setzer, Wandering Gambler|Final Fantasy Commander|183|R|{1}{B}{R}|Legendary Creature - Human Rogue Pilot|2|2|When Setzer enters, create The Blackjack, a legendary 3/3 colorless Vehicle artifact token with flying and crew 2.$Whenever a Vehicle you control deals combat damage to a player, flip a coin.$Whenever you win a coin flip, create two tapped Treasure tokens.| +Shelinda, Yevon Acolyte|Final Fantasy Commander|184|R|{G}{W}|Legendary Creature - Human Cleric|2|2|Lifelink$Whenever another creature you control enters, put a +1/+1 counter on that creature if its power is less than Shelinda's power. Otherwise, put a +1/+1 counter on Shelinda.| +Sin, Unending Cataclysm|Final Fantasy Commander|185|R|{5}{G}{U}|Legendary Creature - Leviathan Avatar|5|5|Flying, trample$As Sin enters, remove all counters from any number of artifacts, creatures, and enchantments. Sin enters with X+1/+1 counters on it, where X is twice the number of counters removed this way.$When Sin dies, put its counters on target creature you control, then shuffle this card into its owner's library.| +Terra, Herald of Hope|Final Fantasy Commander|186|M|{R}{W}{B}|Legendary Creature - Human Wizard Warrior|3|3|Trance -- At the beginning of combat on your turn, mill two cards. Terra gains flying until end of turn.$Whenever Terra deals combat damage to a player, you may pay {2}. When you do, return target creature card with power 3 or less from your graveyard to the battlefield tapped.| +Tidus, Yuna's Guardian|Final Fantasy Commander|187|M|{G}{W}{U}|Legendary Creature - Human Warrior|3|3|At the beginning of combat on your turn, you may move a counter from target creature you control onto a second target creature you control.$Cheer -- Whenever one or more creatures you control with counters on them deal combat damage to a player, you may draw a card and proliferate. Do this only once each turn.| +Tifa, Martial Artist|Final Fantasy Commander|188|M|{1}{R}{G}{W}|Legendary Creature - Human Monk|4|4|Melee$Whenever one or more creatures you control with power 7 or greater deal combat damage to a player, untap all creatures you control. If it's the first combat phase of your turn, there is an additional combat phase after this phase.| +Urianger Augurelt|Final Fantasy Commander|189|R|{W}{U}|Legendary Creature - Elf Advisor|1|3|Whenever you play a land from exile or cast a spell from exile, you gain 2 life.$Draw Arcanum -- {T}: Look at the top card of your library. You may exile it face down.$Play Arcanum -- {T}: Until end of turn, you may play cards exiled with Urianger Augurelt. Spells you cast this way cost {2} less to cast.| +Wakka, Devoted Guardian|Final Fantasy Commander|190|R|{2}{G}{W}|Legendary Creature - Human Warrior|4|4|Reach, trample$Whenever Wakka deals combat damage to a player, destroy up to one target artifact that player controls and put a +1/+1 counter on Wakka.$Blitzball Captain -- At the beginning of your end step, if a counter was put on Wakka this turn, put a +1/+1 counter on each other creature you control.| +Y'shtola, Night's Blessed|Final Fantasy Commander|191|M|{1}{W}{U}{B}|Legendary Creature - Cat Warlock|2|4|Vigilance$At the beginning of each end step, if a player lost 4 or more life this turn, you draw a card.$Whenever you cast a noncreature spell with mana value 3 or greater, Y'shtola deals 2 damage to each opponent and you gain 2 life.| +Yuna, Grand Summoner|Final Fantasy Commander|192|M|{1}{G}{W}{U}|Legendary Creature - Human Cleric|1|5|Grand Summon -- {T}: Add one mana of any color. When you next cast a creature spell this turn, that creature enters with two additional +1/+1 counters on it.$Whenever another permanent you control is put into a graveyard from the battlefield, if it had one or more counters on it, you may put that number of +1/+1 counters on target creature.| +The Warring Triad|Final Fantasy Commander|193|R|{3}|Legendary Artifact Creature - God|5|5|Flying, trample, haste$As long as there are fewer than eight cards in your graveyard, The Warring Triad isn't a creature.${T}, Mill a card: Target player adds one mana of any color.| +Summon: Good King Mog XII|Final Fantasy Commander|194|R|{4}{W}|Enchantment Creature - Saga Moogle|4|4|(As this Saga enters and after your draw step, add a lore counter. Sacrifice after IV.)$I - Create two 1/2 white Moogle creature tokens with lifelink.$II, III - Whenever you cast a noncreature spell this turn, create a token that's a copy of a non-Saga token you control.$IV - Put two +1/+1 counters on each other Moogle you control.$Flying, lifelink| +Summon: Ixion|Final Fantasy Commander|195|R|{2}{W}|Enchantment Creature - Saga Unicorn|3|3|(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I - Aerospark -- Exile target creature an opponent controls until this Saga leaves the battlefield.$II, III - Put a +1/+1 counter on each of up to two target creatures you control. You gain 2 life.$First strike| +Summon: Yojimbo|Final Fantasy Commander|196|R|{3}{W}|Enchantment Creature - Saga Samurai|5|5|(As this Saga enters and after your draw step, add a lore counter. Sacrifice after IV.)$I - Exile target artifact, enchantment, or tapped creature an opponent controls.$II, III - Until your next turn, creatures can't attack you unless their controller pays {2} for each of those creatures.$IV - Create X Treasure tokens, where X is the number of opponents who control a creature with power 4 or greater.$Vigilance| +Summon: Valefor|Final Fantasy Commander|197|R|{4}{U}|Enchantment Creature - Saga Drake|5|4|(As this Saga enters and after your draw step, add a lore counter. Sacrifice after IV.)$I - Sonic Wings -- Each opponent chooses a creature with the greatest mana value among creatures they control. Return those creatures to their owners' hands.$II, III, IV - Tap up to one target creature and put a stun counter on it.$Flying| +Summon: Esper Valigarmanda|Final Fantasy Commander|198|R|{3}{R}|Enchantment Creature - Saga Drake|3|3|(As this Saga enters and after your draw step, add a lore counter. Sacrifice after IV.)$I -- Exile an instant or sorcery card from each graveyard.$II, III, IV -- Add {R} for each lore counter on this Saga. You may cast an instant or sorcery card exiled with this Saga, and mana of any type can be spent to cast that spell.$Flying, haste| +Summon: Kujata|Final Fantasy Commander|199|R|{5}{R}|Enchantment Creature - Saga Ox|7|5|(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I -- Lightning -- This creature deals 3 damage to each of up to two target creatures.$II -- Ice -- Up to three target creatures can't block this turn.$III -- Fire -- Discard a card, then draw two cards. When you discard a card this way, this creature deals damage equal to that card's mana value to each opponent.$Trample, haste| +Summon: Magus Sisters|Final Fantasy Commander|200|R|{4}{G}|Enchantment Creature - Saga Faerie|5|5|(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I,II,III - Choose one at random --$* Combine Powers! -- Put three +1/+1 counters on target creature.$* Defense! -- Put a shield counter on target creature. You gain 3 life.$* Fight! -- This creature fights up to one target creature an opponent controls.$Haste| +Celes, Rune Knight|Final Fantasy Commander|201|M|{1}{R}{W}{B}|Legendary Creature - Human Wizard Knight|4|4|When Celes enters, discard any number of cards, then draw that many cards plus one.$Whenever one or more other creatures you control enter, if one or more of them entered from a graveyard or was cast from a graveyard, put a +1/+1 counter on each creature you control.| +Cloud, Ex-SOLDIER|Final Fantasy Commander|202|M|{2}{R}{G}{W}|Legendary Creature - Human Soldier Mercenary|4|4|Haste$When Cloud enters, attach up to one target Equipment you control to it.$Whenever Cloud attacks, draw a card for each equipped attacking creature you control. Then if Cloud has power 7 or greater, create two Treasure tokens.| +G'raha Tia, Scion Reborn|Final Fantasy Commander|203|M|{W}{U}{B}|Legendary Creature - Cat Wizard|2|3|Lifelink$Throw Wide the Gates -- Whenever you cast a noncreature spell, you may pay X life, where X is that spell's mana value. If you do, create a 1/1 colorless Hero creature token and put X+1/+1 counters on it. Do this only once each turn.| +Terra, Herald of Hope|Final Fantasy Commander|204|M|{R}{W}{B}|Legendary Creature - Human Wizard Warrior|3|3|Trance -- At the beginning of combat on your turn, mill two cards. Terra gains flying until end of turn.$Whenever Terra deals combat damage to a player, you may pay {2}. When you do, return target creature card with power 3 or less from your graveyard to the battlefield tapped.| +Tidus, Yuna's Guardian|Final Fantasy Commander|205|M|{G}{W}{U}|Legendary Creature - Human Warrior|3|3|At the beginning of combat on your turn, you may move a counter from target creature you control onto a second target creature you control.$Cheer -- Whenever one or more creatures you control with counters on them deal combat damage to a player, you may draw a card and proliferate. Do this only once each turn.| +Tifa, Martial Artist|Final Fantasy Commander|206|M|{1}{R}{G}{W}|Legendary Creature - Human Monk|4|4|Melee$Whenever one or more creatures you control with power 7 or greater deal combat damage to a player, untap all creatures you control. If it's the first combat phase of your turn, there is an additional combat phase after this phase.| +Y'shtola, Night's Blessed|Final Fantasy Commander|207|M|{1}{W}{U}{B}|Legendary Creature - Cat Warlock|2|4|Vigilance$At the beginning of each end step, if a player lost 4 or more life this turn, you draw a card.$Whenever you cast a noncreature spell with mana value 3 or greater, Y'shtola deals 2 damage to each opponent and you gain 2 life.| +Yuna, Grand Summoner|Final Fantasy Commander|208|M|{1}{G}{W}{U}|Legendary Creature - Human Cleric|1|5|Grand Summon -- {T}: Add one mana of any color. When you next cast a creature spell this turn, that creature enters with two additional +1/+1 counters on it.$Whenever another permanent you control is put into a graveyard from the battlefield, if it had one or more counters on it, you may put that number of +1/+1 counters on target creature.| +Celes, Rune Knight|Final Fantasy Commander|209|M|{1}{R}{W}{B}|Legendary Creature - Human Wizard Knight|4|4|When Celes enters, discard any number of cards, then draw that many cards plus one.$Whenever one or more other creatures you control enter, if one or more of them entered from a graveyard or was cast from a graveyard, put a +1/+1 counter on each creature you control.| +Cloud, Ex-SOLDIER|Final Fantasy Commander|210|M|{2}{R}{G}{W}|Legendary Creature - Human Soldier Mercenary|4|4|Haste$When Cloud enters, attach up to one target Equipment you control to it.$Whenever Cloud attacks, draw a card for each equipped attacking creature you control. Then if Cloud has power 7 or greater, create two Treasure tokens.| +G'raha Tia, Scion Reborn|Final Fantasy Commander|211|M|{W}{U}{B}|Legendary Creature - Cat Wizard|2|3|Lifelink$Throw Wide the Gates -- Whenever you cast a noncreature spell, you may pay X life, where X is that spell's mana value. If you do, create a 1/1 colorless Hero creature token and put X+1/+1 counters on it. Do this only once each turn.| +Terra, Herald of Hope|Final Fantasy Commander|212|M|{R}{W}{B}|Legendary Creature - Human Wizard Warrior|3|3|Trance -- At the beginning of combat on your turn, mill two cards. Terra gains flying until end of turn.$Whenever Terra deals combat damage to a player, you may pay {2}. When you do, return target creature card with power 3 or less from your graveyard to the battlefield tapped.| +Tidus, Yuna's Guardian|Final Fantasy Commander|213|M|{G}{W}{U}|Legendary Creature - Human Warrior|3|3|At the beginning of combat on your turn, you may move a counter from target creature you control onto a second target creature you control.$Cheer -- Whenever one or more creatures you control with counters on them deal combat damage to a player, you may draw a card and proliferate. Do this only once each turn.| +Tifa, Martial Artist|Final Fantasy Commander|214|M|{1}{R}{G}{W}|Legendary Creature - Human Monk|4|4|Melee$Whenever one or more creatures you control with power 7 or greater deal combat damage to a player, untap all creatures you control. If it's the first combat phase of your turn, there is an additional combat phase after this phase.| +Y'shtola, Night's Blessed|Final Fantasy Commander|215|M|{1}{W}{U}{B}|Legendary Creature - Cat Warlock|2|4|Vigilance$At the beginning of each end step, if a player lost 4 or more life this turn, you draw a card.$Whenever you cast a noncreature spell with mana value 3 or greater, Y'shtola deals 2 damage to each opponent and you gain 2 life.| +Yuna, Grand Summoner|Final Fantasy Commander|216|M|{1}{G}{W}{U}|Legendary Creature - Human Cleric|1|5|Grand Summon -- {T}: Add one mana of any color. When you next cast a creature spell this turn, that creature enters with two additional +1/+1 counters on it.$Whenever another permanent you control is put into a graveyard from the battlefield, if it had one or more counters on it, you may put that number of +1/+1 counters on target creature.| +Secret Rendezvous|Final Fantasy Commander|217|U|{1}{W}{W}|Sorcery|||You and target opponent each draw three cards.| +Secret Rendezvous|Final Fantasy Commander|218|U|{1}{W}{W}|Sorcery|||You and target opponent each draw three cards.| +Secret Rendezvous|Final Fantasy Commander|219|U|{1}{W}{W}|Sorcery|||You and target opponent each draw three cards.| +Celes, Rune Knight|Final Fantasy Commander|220|M|{1}{R}{W}{B}|Legendary Creature - Human Wizard Knight|4|4|When Celes enters, discard any number of cards, then draw that many cards plus one.$Whenever one or more other creatures you control enter, if one or more of them entered from a graveyard or was cast from a graveyard, put a +1/+1 counter on each creature you control.| +Cloud, Ex-SOLDIER|Final Fantasy Commander|221|M|{2}{R}{G}{W}|Legendary Creature - Human Soldier Mercenary|4|4|Haste$When Cloud enters, attach up to one target Equipment you control to it.$Whenever Cloud attacks, draw a card for each equipped attacking creature you control. Then if Cloud has power 7 or greater, create two Treasure tokens.| +G'raha Tia, Scion Reborn|Final Fantasy Commander|222|M|{W}{U}{B}|Legendary Creature - Cat Wizard|2|3|Lifelink$Throw Wide the Gates -- Whenever you cast a noncreature spell, you may pay X life, where X is that spell's mana value. If you do, create a 1/1 colorless Hero creature token and put X+1/+1 counters on it. Do this only once each turn.| +Terra, Herald of Hope|Final Fantasy Commander|223|M|{R}{W}{B}|Legendary Creature - Human Wizard Warrior|3|3|Trance -- At the beginning of combat on your turn, mill two cards. Terra gains flying until end of turn.$Whenever Terra deals combat damage to a player, you may pay {2}. When you do, return target creature card with power 3 or less from your graveyard to the battlefield tapped.| +Tidus, Yuna's Guardian|Final Fantasy Commander|224|M|{G}{W}{U}|Legendary Creature - Human Warrior|3|3|At the beginning of combat on your turn, you may move a counter from target creature you control onto a second target creature you control.$Cheer -- Whenever one or more creatures you control with counters on them deal combat damage to a player, you may draw a card and proliferate. Do this only once each turn.| +Tifa, Martial Artist|Final Fantasy Commander|225|M|{1}{R}{G}{W}|Legendary Creature - Human Monk|4|4|Melee$Whenever one or more creatures you control with power 7 or greater deal combat damage to a player, untap all creatures you control. If it's the first combat phase of your turn, there is an additional combat phase after this phase.| +Y'shtola, Night's Blessed|Final Fantasy Commander|226|M|{1}{W}{U}{B}|Legendary Creature - Cat Warlock|2|4|Vigilance$At the beginning of each end step, if a player lost 4 or more life this turn, you draw a card.$Whenever you cast a noncreature spell with mana value 3 or greater, Y'shtola deals 2 damage to each opponent and you gain 2 life.| +Yuna, Grand Summoner|Final Fantasy Commander|227|M|{1}{G}{W}{U}|Legendary Creature - Human Cleric|1|5|Grand Summon -- {T}: Add one mana of any color. When you next cast a creature spell this turn, that creature enters with two additional +1/+1 counters on it.$Whenever another permanent you control is put into a graveyard from the battlefield, if it had one or more counters on it, you may put that number of +1/+1 counters on target creature.| +Herald's Horn|Final Fantasy Commander|228|R|{3}|Artifact|||As this artifact enters, choose a creature type.$Creature spells you cast of the chosen type cost {1} less to cast.$At the beginning of your upkeep, look at the top card of your library. If it's a creature card of the chosen type, you may reveal it and put it into your hand.| +Angel of the Ruins|Final Fantasy Commander|229|U|{5}{W}{W}|Artifact Creature - Angel|5|7|Flying$When this creature enters, exile up to two target artifacts and/or enchantments.$Plainscycling {2}| +Archaeomancer's Map|Final Fantasy Commander|230|R|{2}{W}|Artifact|||When this artifact enters, search your library for up to two basic Plains cards, reveal them, put them into your hand, then shuffle.$Whenever a land an opponent controls enters, if that player controls more lands than you, you may put a land card from your hand onto the battlefield.| +Austere Command|Final Fantasy Commander|231|R|{4}{W}{W}|Sorcery|||Choose two --$* Destroy all artifacts.$* Destroy all enchantments.$* Destroy all creatures with mana value 3 or less.$* Destroy all creatures with mana value 4 or greater.| +Authority of the Consuls|Final Fantasy Commander|232|R|{W}|Enchantment|||Creatures your opponents control enter tapped.$Whenever a creature an opponent controls enters, you gain 1 life.| +Bastion Protector|Final Fantasy Commander|233|R|{2}{W}|Creature - Human Soldier|3|3|Commander creatures you control get +2/+2 and have indestructible.| +Bronze Guardian|Final Fantasy Commander|234|R|{4}{W}|Artifact Creature - Golem|*|5|Double strike$Ward {2}$Other artifacts you control have ward {2}.$Bronze Guardian's power is equal to the number of artifacts you control.| +Cleansing Nova|Final Fantasy Commander|235|R|{3}{W}{W}|Sorcery|||Choose one --$* Destroy all creatures.$* Destroy all artifacts and enchantments.| +Clever Concealment|Final Fantasy Commander|236|R|{2}{W}{W}|Instant|||Convoke$Any number of target nonland permanents you control phase out.| +Collective Effort|Final Fantasy Commander|237|R|{1}{W}{W}|Sorcery|||Escalate--Tap an untapped creature you control.$Choose one or more --$* Destroy target creature with power 4 or greater.$* Destroy target enchantment.$* Put a +1/+1 counter on each creature target player controls.| +Cut a Deal|Final Fantasy Commander|238|U|{2}{W}|Sorcery|||Each opponent draws a card, then you draw a card for each opponent who drew a card this way.| +Damning Verdict|Final Fantasy Commander|239|R|{3}{W}{W}|Sorcery|||Destroy all creatures with no counters on them.| +Destroy Evil|Final Fantasy Commander|240|C|{1}{W}|Instant|||Choose one --$* Destroy target creature with toughness 4 or greater.$* Destroy target enchantment.| +Dispatch|Final Fantasy Commander|241|U|{W}|Instant|||Tap target creature.$Metalcraft -- If you control three or more artifacts, exile that creature.| +Farewell|Final Fantasy Commander|242|R|{4}{W}{W}|Sorcery|||Choose one or more --$* Exile all artifacts.$* Exile all creatures.$* Exile all enchantments.$* Exile all graveyards.| +Final Judgment|Final Fantasy Commander|243|M|{4}{W}{W}|Sorcery|||Exile all creatures.| +Grateful Apparition|Final Fantasy Commander|244|U|{1}{W}|Creature - Spirit|1|1|Flying$Whenever this creature deals combat damage to a player or planeswalker, proliferate.| +Lingering Souls|Final Fantasy Commander|245|U|{2}{W}|Sorcery|||Create two 1/1 white Spirit creature tokens with flying.$Flashback {1}{B}| +Luminous Broodmoth|Final Fantasy Commander|246|M|{2}{W}{W}|Creature - Insect|3|4|Flying$Whenever a creature you control without flying dies, return it to the battlefield under its owner's control with a flying counter on it.| +Palace Jailer|Final Fantasy Commander|247|U|{2}{W}{W}|Creature - Human Soldier|2|2|When this creature enters, you become the monarch.$When this creature enters, exile target creature an opponent controls until an opponent becomes the monarch.| +Path to Exile|Final Fantasy Commander|248|U|{W}|Instant|||Exile target creature. Its controller may search their library for a basic land card, put that card onto the battlefield tapped, then shuffle.| +Promise of Loyalty|Final Fantasy Commander|249|R|{4}{W}|Sorcery|||Each player puts a vow counter on a creature they control and sacrifices the rest. Each of those creatures can't attack you or planeswalkers you control for as long as it has a vow counter on it.| +Puresteel Paladin|Final Fantasy Commander|250|R|{W}{W}|Creature - Human Knight|2|2|Whenever an Equipment you control enters, you may draw a card.$Metalcraft -- Equipment you control have equip {0} as long as you control three or more artifacts.| +Resourceful Defense|Final Fantasy Commander|251|R|{2}{W}|Enchantment|||Whenever a permanent you control leaves the battlefield, if it had counters on it, put those counters on target permanent you control.${4}{W}: Move any number of counters from target permanent you control to another target permanent you control.| +Scholar of New Horizons|Final Fantasy Commander|252|R|{1}{W}|Creature - Human Scout|1|1|This creature enters with a +1/+1 counter on it.${T}, Remove a counter from a permanent you control: Search your library for a Plains card and reveal it. If an opponent controls more lands than you, you may put that card onto the battlefield tapped. If you don't put the card onto the battlefield, put it into your hand. Then shuffle.| +Secret Rendezvous|Final Fantasy Commander|253|U|{1}{W}{W}|Sorcery|||You and target opponent each draw three cards.| +Sun Titan|Final Fantasy Commander|254|M|{4}{W}{W}|Creature - Giant|6|6|Vigilance$Whenever this creature enters or attacks, you may return target permanent card with mana value 3 or less from your graveyard to the battlefield.| +Sunscorch Regent|Final Fantasy Commander|255|R|{3}{W}{W}|Creature - Dragon|4|3|Flying$Whenever an opponent casts a spell, put a +1/+1 counter on this creature and you gain 1 life.| +Swords to Plowshares|Final Fantasy Commander|256|U|{W}|Instant|||Exile target creature. Its controller gains life equal to its power.| +Together Forever|Final Fantasy Commander|257|R|{W}{W}|Enchantment|||When this enchantment enters, support 2.${1}: Choose target creature with a counter on it. When that creature dies this turn, return that card to its owner's hand.| +Tragic Arrogance|Final Fantasy Commander|258|R|{3}{W}{W}|Sorcery|||For each player, you choose from among the permanents that player controls an artifact, a creature, an enchantment, and a planeswalker. Then each player sacrifices all other nonland permanents they control.| +Unfinished Business|Final Fantasy Commander|259|R|{3}{W}{W}|Sorcery|||Return target creature card from your graveyard to the battlefield, then return up to two target Aura and/or Equipment cards from your graveyard to the battlefield attached to that creature.| +Vanquish the Horde|Final Fantasy Commander|260|R|{6}{W}{W}|Sorcery|||This spell costs {1} less to cast for each creature on the battlefield.$Destroy all creatures.| +Archmage Emeritus|Final Fantasy Commander|261|R|{2}{U}{U}|Creature - Human Wizard|2|2|Magecraft -- Whenever you cast or copy an instant or sorcery spell, draw a card.| +Chasm Skulker|Final Fantasy Commander|262|R|{2}{U}|Creature - Squid Horror|1|1|Whenever you draw a card, put a +1/+1 counter on this creature.$When this creature dies, create X 1/1 blue Squid creature tokens with islandwalk, where X is the number of +1/+1 counters on this creature.| +Dig Through Time|Final Fantasy Commander|263|R|{6}{U}{U}|Instant|||Delve$Look at the top seven cards of your library. Put two of them into your hand and the rest on the bottom of your library in any order.| +Hypnotic Sprite|Final Fantasy Commander|264|U|{U}{U}|Creature - Faerie|2|1|Flying| +Mesmeric Glare|Final Fantasy Commander|264|U|{2}{U}|Instant - Adventure|2|1|Counter target spell with mana value 3 or less.| +Inexorable Tide|Final Fantasy Commander|265|R|{3}{U}{U}|Enchantment|||Whenever you cast a spell, proliferate.| +Into the Story|Final Fantasy Commander|266|U|{5}{U}{U}|Instant|||This spell costs {3} less to cast if an opponent has seven or more cards in their graveyard.$Draw four cards.| +An Offer You Can't Refuse|Final Fantasy Commander|267|U|{U}|Instant|||Counter target noncreature spell. Its controller creates two Treasure tokens.| +Propaganda|Final Fantasy Commander|268|U|{2}{U}|Enchantment|||Creatures can't attack you unless their controller pays {2} for each creature they control that's attacking you.| +Pull from Tomorrow|Final Fantasy Commander|269|R|{X}{U}{U}|Instant|||Draw X cards, then discard a card.| +Rite of Replication|Final Fantasy Commander|270|R|{2}{U}{U}|Sorcery|||Kicker {5}$Create a token that's a copy of target creature. If this spell was kicked, create five of those tokens instead.| +Sublime Epiphany|Final Fantasy Commander|271|R|{4}{U}{U}|Instant|||Choose one or more --$* Counter target spell.$* Counter target activated or triggered ability.$* Return target nonland permanent to its owner's hand.$* Create a token that's a copy of target creature you control.$* Target player draws a card.| +Torrential Gearhulk|Final Fantasy Commander|272|R|{4}{U}{U}|Artifact Creature - Construct|5|6|Flash$When this creature enters, you may cast target instant card from your graveyard without paying its mana cost. If that spell would be put into your graveyard, exile it instead.| +Archfiend of Depravity|Final Fantasy Commander|273|R|{3}{B}{B}|Creature - Demon|5|4|Flying$At the beginning of each opponent's end step, that player chooses up to two creatures they control, then sacrifices the rest.| +Bastion of Remembrance|Final Fantasy Commander|274|U|{2}{B}|Enchantment|||When this enchantment enters, create a 1/1 white Human Soldier creature token.$Whenever a creature you control dies, each opponent loses 1 life and you gain 1 life.| +Crux of Fate|Final Fantasy Commander|275|R|{3}{B}{B}|Sorcery|||Choose one --$* Destroy all Dragon creatures.$* Destroy all non-Dragon creatures.| +Exsanguinate|Final Fantasy Commander|276|U|{X}{B}{B}|Sorcery|||Each opponent loses X life. You gain life equal to the life lost this way.| +Lethal Scheme|Final Fantasy Commander|277|R|{2}{B}{B}|Instant|||Convoke$Destroy target creature or planeswalker. Each creature that convoked this spell connives.| +Morbid Opportunist|Final Fantasy Commander|278|U|{2}{B}|Creature - Human Rogue|1|3|Whenever one or more other creatures die, draw a card. This ability triggers only once each turn.| +Murderous Rider|Final Fantasy Commander|279|R|{1}{B}{B}|Creature - Zombie Knight|2|3|Lifelink$When this creature dies, put it on the bottom of its owner's library.| +Swift End|Final Fantasy Commander|279|R|{1}{B}{B}|Instant - Adventure|2|3|Destroy target creature or planeswalker. You lose 2 life.| +Night's Whisper|Final Fantasy Commander|280|C|{1}{B}|Sorcery|||You draw two cards and you lose 2 life.| +Pitiless Plunderer|Final Fantasy Commander|281|U|{3}{B}|Creature - Human Pirate|1|4|Whenever another creature you control dies, create a Treasure token.| +Reanimate|Final Fantasy Commander|282|R|{B}|Sorcery|||Put target creature card from a graveyard onto the battlefield under your control. You lose life equal to its mana value.| +Rise of the Dark Realms|Final Fantasy Commander|283|M|{7}{B}{B}|Sorcery|||Put all creature cards from all graveyards onto the battlefield under your control.| +Sepulchral Primordial|Final Fantasy Commander|284|R|{5}{B}{B}|Creature - Avatar|5|4|Intimidate$When this creature enters, for each opponent, you may put up to one target creature card from that player's graveyard onto the battlefield under your control.| +Snuff Out|Final Fantasy Commander|285|C|{3}{B}|Instant|||If you control a Swamp, you may pay 4 life rather than pay this spell's mana cost.$Destroy target nonblack creature. It can't be regenerated.| +Stitch Together|Final Fantasy Commander|286|U|{B}{B}|Sorcery|||Return target creature card from your graveyard to your hand.$Threshold -- Return that card from your graveyard to the battlefield instead if seven or more cards are in your graveyard.| +Stitcher's Supplier|Final Fantasy Commander|287|U|{B}|Creature - Zombie|1|1|When this creature enters or dies, mill three cards.| +Syphon Mind|Final Fantasy Commander|288|C|{3}{B}|Sorcery|||Each other player discards a card. You draw a card for each card discarded this way.| +Anger|Final Fantasy Commander|289|U|{3}{R}|Creature - Incarnation|2|2|Haste$As long as this card is in your graveyard and you control a Mountain, creatures you control have haste.| +Big Score|Final Fantasy Commander|290|C|{3}{R}|Instant|||As an additional cost to cast this spell, discard a card.$Draw two cards and create two Treasure tokens.| +Chaos Warp|Final Fantasy Commander|291|R|{2}{R}|Instant|||The owner of target permanent shuffles it into their library, then reveals the top card of their library. If it's a permanent card, they put it onto the battlefield.| +Combustible Gearhulk|Final Fantasy Commander|292|M|{4}{R}{R}|Artifact Creature - Construct|6|6|First strike$When this creature enters, target opponent may have you draw three cards. If the player doesn't, you mill three cards, then this creature deals damage to that player equal to the total mana value of those cards.| +Flayer of the Hatebound|Final Fantasy Commander|293|R|{5}{R}|Creature - Devil|4|2|Undying$Whenever this creature or another creature enters from your graveyard, that creature deals damage equal to its power to any target.| +Furious Rise|Final Fantasy Commander|294|U|{2}{R}|Enchantment|||At the beginning of your end step, if you control a creature with power 4 or greater, exile the top card of your library. You may play that card until you exile another card with this enchantment.| +Hellkite Tyrant|Final Fantasy Commander|295|R|{4}{R}{R}|Creature - Dragon|6|5|Flying, trample$Whenever Hellkite Tyrant deals combat damage to a player, gain control of all artifacts that player controls.$At the beginning of your upkeep, if you control twenty or more artifacts, you win the game.| +Professional Face-Breaker|Final Fantasy Commander|296|R|{2}{R}|Creature - Human Warrior|2|3|Menace$Whenever one or more creatures you control deal combat damage to a player, create a Treasure token.$Sacrifice a Treasure: Exile the top card of your library. You may play that card this turn.| +Ruin Grinder|Final Fantasy Commander|297|R|{5}{R}|Artifact Creature - Construct|7|4|Menace$When this creature dies, each player may discard their hand and draw seven cards.$Mountaincycling {2}| +Vandalblast|Final Fantasy Commander|298|U|{R}|Sorcery|||Destroy target artifact you don't control.$Overload {4}{R}| +Bane of Progress|Final Fantasy Commander|299|R|{4}{G}{G}|Creature - Elemental|2|2|When this creature enters, destroy all artifacts and enchantments. Put a +1/+1 counter on this creature for each permanent destroyed this way.| +Cultivate|Final Fantasy Commander|300|C|{2}{G}|Sorcery|||Search your library for up to two basic land cards, reveal those cards, put one onto the battlefield tapped and the other into your hand, then shuffle.| +Duskshell Crawler|Final Fantasy Commander|301|C|{1}{G}|Creature - Insect|0|3|When this creature enters, put a +1/+1 counter on target creature.$Each creature you control with a +1/+1 counter on it has trample.| +Farseek|Final Fantasy Commander|302|C|{1}{G}|Sorcery|||Search your library for a Plains, Island, Swamp, or Mountain card, put it onto the battlefield tapped, then shuffle.| +Fight Rigging|Final Fantasy Commander|303|R|{2}{G}|Enchantment|||Hideaway 5$At the beginning of combat on your turn, put a +1/+1 counter on target creature you control. Then if you control a creature with power 7 or greater, you may play the exiled card without paying its mana cost.| +Forgotten Ancient|Final Fantasy Commander|304|R|{3}{G}|Creature - Elemental|0|3|Whenever a player casts a spell, you may put a +1/+1 counter on this creature.$At the beginning of your upkeep, you may move any number of +1/+1 counters from this creature onto other creatures.| +Generous Patron|Final Fantasy Commander|305|R|{2}{G}|Creature - Elf Advisor|1|4|When this creature enters, support 2.$Whenever you put one or more counters on a creature you don't control, draw a card.| +Gyre Sage|Final Fantasy Commander|306|R|{1}{G}|Creature - Elf Druid|1|2|Evolve${T}: Add {G} for each +1/+1 counter on this creature.| +Hardened Scales|Final Fantasy Commander|307|R|{G}|Enchantment|||If one or more +1/+1 counters would be put on a creature you control, that many plus one +1/+1 counters are put on it instead.| +Harmonize|Final Fantasy Commander|308|U|{2}{G}{G}|Sorcery|||Draw three cards.| +Incubation Druid|Final Fantasy Commander|309|R|{1}{G}|Creature - Elf Druid|0|2|{T}: Add one mana of any type that a land you control could produce. If this creature has a +1/+1 counter on it, add three mana of that type instead.${3}{G}{G}: Adapt 3.| +Inspiring Call|Final Fantasy Commander|310|U|{2}{G}|Instant|||Draw a card for each creature you control with a +1/+1 counter on it. Those creatures gain indestructible until end of turn.| +Nature's Lore|Final Fantasy Commander|311|C|{1}{G}|Sorcery|||Search your library for a Forest card, put that card onto the battlefield, then shuffle.| +Path of Discovery|Final Fantasy Commander|312|R|{3}{G}|Enchantment|||Whenever a creature you control enters, it explores.| +Rampant Growth|Final Fantasy Commander|313|C|{1}{G}|Sorcery|||Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle.| +Rampant Rejuvenator|Final Fantasy Commander|314|R|{3}{G}|Creature - Plant Hydra|0|0|This creature enters with two +1/+1 counters on it.$When this creature dies, search your library for up to X basic land cards, where X is this creature's power, put them onto the battlefield, then shuffle.| +Three Visits|Final Fantasy Commander|315|U|{1}{G}|Sorcery|||Search your library for a Forest card, put it onto the battlefield, then shuffle.| +Tireless Tracker|Final Fantasy Commander|316|R|{2}{G}|Creature - Human Scout|3|2|Landfall -- Whenever a land you control enters, investigate.$Whenever you sacrifice a Clue, put a +1/+1 counter on this creature.| +Altered Ego|Final Fantasy Commander|317|R|{X}{2}{G}{U}|Creature - Shapeshifter|0|0|This spell can't be countered.$You may have this creature enter as a copy of any creature on the battlefield, except it enters with X additional +1/+1 counters on it.| +Baleful Strix|Final Fantasy Commander|318|R|{U}{B}|Artifact Creature - Bird|1|1|Flying, deathtouch$When this creature enters, draw a card.| +Bedevil|Final Fantasy Commander|319|R|{B}{B}{R}|Instant|||Destroy target artifact, creature, or planeswalker.| +Behemoth Sledge|Final Fantasy Commander|320|U|{1}{G}{W}|Artifact - Equipment|||Equipped creature gets +2/+2 and has trample and lifelink.$Equip {3}| +Bred for the Hunt|Final Fantasy Commander|321|U|{1}{G}{U}|Enchantment|||Whenever a creature you control with a +1/+1 counter on it deals combat damage to a player, you may draw a card.| +Crackling Doom|Final Fantasy Commander|322|U|{R}{W}{B}|Instant|||Crackling Doom deals 2 damage to each opponent. Each opponent sacrifices a creature with the greatest power among creatures that player controls.| +Decimate|Final Fantasy Commander|323|R|{2}{R}{G}|Sorcery|||Destroy target artifact, target creature, target enchantment, and target land.| +Endless Detour|Final Fantasy Commander|324|R|{G}{W}{U}|Instant|||The owner of target spell, nonland permanent, or card in a graveyard puts it on their choice of the top or bottom of their library.| +Fathom Mage|Final Fantasy Commander|325|R|{2}{G}{U}|Creature - Human Wizard|1|1|Evolve$Whenever a +1/+1 counter is put on this creature, you may draw a card.| +Legions to Ashes|Final Fantasy Commander|326|R|{1}{W}{B}|Sorcery|||Exile target nonland permanent an opponent controls and all tokens that player controls with the same name as that permanent.| +Mortify|Final Fantasy Commander|327|U|{1}{W}{B}|Instant|||Destroy target creature or enchantment.| +Priest of Fell Rites|Final Fantasy Commander|328|R|{W}{B}|Creature - Human Warlock|2|2|{T}, Pay 3 life, Sacrifice this creature: Return target creature card from your graveyard to the battlefield. Activate only as a sorcery.$Unearth {3}{W}{B}| +Ruinous Ultimatum|Final Fantasy Commander|329|R|{R}{R}{W}{W}{W}{B}{B}|Sorcery|||Destroy all nonland permanents your opponents control.| +Vindicate|Final Fantasy Commander|330|R|{1}{W}{B}|Sorcery|||Destroy target permanent.| +Void Rend|Final Fantasy Commander|331|R|{W}{U}{B}|Instant|||This spell can't be countered.$Destroy target nonland permanent.| +Arcane Signet|Final Fantasy Commander|332|C|{2}|Artifact|||{T}: Add one mana of any color in your commander's color identity.| +Arcane Signet|Final Fantasy Commander|333|C|{2}|Artifact|||{T}: Add one mana of any color in your commander's color identity.| +Arcane Signet|Final Fantasy Commander|334|C|{2}|Artifact|||{T}: Add one mana of any color in your commander's color identity.| +Arcane Signet|Final Fantasy Commander|335|C|{2}|Artifact|||{T}: Add one mana of any color in your commander's color identity.| +Armory Automaton|Final Fantasy Commander|336|R|{3}|Artifact Creature - Construct|2|2|Whenever this creature enters or attacks, you may attach any number of target Equipment to it.| +Champion's Helm|Final Fantasy Commander|337|R|{3}|Artifact - Equipment|||Equipped creature gets +2/+2.$As long as equipped creature is legendary, it has hexproof.$Equip {1}| +Colossus Hammer|Final Fantasy Commander|338|U|{1}|Artifact - Equipment|||Equipped creature gets +10/+10 and loses flying.$Equip {8}| +Commander's Sphere|Final Fantasy Commander|339|C|{3}|Artifact|||{T}: Add one mana of any color in your commander's color identity.$Sacrifice this artifact: Draw a card.| +Conqueror's Flail|Final Fantasy Commander|340|R|{2}|Artifact - Equipment|||Equipped creature gets +1/+1 for each color among permanents you control.$As long as this Equipment is attached to a creature, your opponents can't cast spells during your turn.$Equip {2}| +Coveted Jewel|Final Fantasy Commander|341|R|{6}|Artifact|||When this artifact enters, draw three cards.${T}: Add three mana of any one color.$Whenever one or more creatures an opponent controls attack you and aren't blocked, that player draws three cards and gains control of this artifact. Untap it.| +Darksteel Plate|Final Fantasy Commander|342|R|{3}|Artifact - Equipment|||Indestructible$Equipped creature has indestructible.$Equip {2}| +Everflowing Chalice|Final Fantasy Commander|343|U|{0}|Artifact|||Multikicker {2}$This artifact enters with a charge counter on it for each time it was kicked.${T}: Add {C} for each charge counter on this artifact.| +Explorer's Scope|Final Fantasy Commander|344|C|{1}|Artifact - Equipment|||Whenever equipped creature attacks, look at the top card of your library. If it's a land card, you may put it onto the battlefield tapped.$Equip {1}| +Hero's Blade|Final Fantasy Commander|345|U|{2}|Artifact - Equipment|||Equipped creature gets +3/+2.$Whenever a legendary creature you control enters, you may attach this Equipment to it.$Equip {4}| +Hero's Heirloom|Final Fantasy Commander|346|U|{2}|Artifact - Equipment|||Equipped creature gets +2/+1.$As long as equipped creature is legendary, it has trample and haste.$Equip {2}| +Inspiring Statuary|Final Fantasy Commander|347|R|{3}|Artifact|||Nonartifact spells you cast have improvise.| +Key to the City|Final Fantasy Commander|348|R|{2}|Artifact|||{T}, Discard a card: Up to one target creature can't be blocked this turn.$Whenever this artifact becomes untapped, you may pay {2}. If you do, draw a card.| +Lightning Greaves|Final Fantasy Commander|349|U|{2}|Artifact - Equipment|||Equipped creature has haste and shroud.$Equip {0}| +Mask of Memory|Final Fantasy Commander|350|U|{2}|Artifact - Equipment|||Whenever equipped creature deals combat damage to a player, you may draw two cards. If you do, discard a card.$Equip {1}| +Meteor Golem|Final Fantasy Commander|351|U|{7}|Artifact Creature - Golem|3|3|When this creature enters, destroy target nonland permanent an opponent controls.| +Millikin|Final Fantasy Commander|352|U|{2}|Artifact Creature - Construct|0|1|{T}, Mill a card: Add {C}.| +Mind Stone|Final Fantasy Commander|353|U|{2}|Artifact|||{T}: Add {C}.${1}, {T}, Sacrifice this artifact: Draw a card.| +Relic of Legends|Final Fantasy Commander|354|U|{3}|Artifact|||{T}: Add one mana of any color.$Tap an untapped legendary creature you control: Add one mana of any color.| +Skullclamp|Final Fantasy Commander|355|U|{1}|Artifact - Equipment|||Equipped creature gets +1/-1.$Whenever equipped creature dies, draw two cards.$Equip {1}| +Sol Ring|Final Fantasy Commander|356|U|{1}|Artifact|||{T}: Add {C}{C}.| +Sol Ring|Final Fantasy Commander|357|U|{1}|Artifact|||{T}: Add {C}{C}.| +Sol Ring|Final Fantasy Commander|358|U|{1}|Artifact|||{T}: Add {C}{C}.| +Sol Ring|Final Fantasy Commander|359|U|{1}|Artifact|||{T}: Add {C}{C}.| +Solemn Simulacrum|Final Fantasy Commander|360|R|{4}|Artifact Creature - Golem|2|2|When this creature enters, you may search your library for a basic land card, put that card onto the battlefield tapped, then shuffle.$When this creature dies, you may draw a card.| +Swiftfoot Boots|Final Fantasy Commander|361|U|{2}|Artifact - Equipment|||Equipped creature has hexproof and haste.$Equip {1}| +Sword of the Animist|Final Fantasy Commander|362|R|{2}|Legendary Artifact - Equipment|||Equipped creature gets +1/+1.$Whenever equipped creature attacks, you may search your library for a basic land card, put it onto the battlefield tapped, then shuffle.$Equip {2}| +Talisman of Conviction|Final Fantasy Commander|363|U|{2}|Artifact|||{T}: Add {C}.${T}: Add {R} or {W}. This artifact deals 1 damage to you.| +Talisman of Dominance|Final Fantasy Commander|364|U|{2}|Artifact|||{T}: Add {C}.${T}: Add {U} or {B}. This artifact deals 1 damage to you.| +Talisman of Hierarchy|Final Fantasy Commander|365|U|{2}|Artifact|||{T}: Add {C}.${T}: Add {W} or {B}. This artifact deals 1 damage to you.| +Talisman of Indulgence|Final Fantasy Commander|366|U|{2}|Artifact|||{T}: Add {C}.${T}: Add {B} or {R}. This artifact deals 1 damage to you.| +Talisman of Progress|Final Fantasy Commander|367|U|{2}|Artifact|||{T}: Add {C}.${T}: Add {W} or {U}. This artifact deals 1 damage to you.| +Thought Vessel|Final Fantasy Commander|368|C|{2}|Artifact|||You have no maximum hand size.${T}: Add {C}.| +Tome of Legends|Final Fantasy Commander|369|R|{2}|Artifact|||This artifact enters with a page counter on it.$Whenever your commander enters or attacks, put a page counter on this artifact.${1}, {T}, Remove a page counter from this artifact: Draw a card.| +Trailblazer's Boots|Final Fantasy Commander|370|U|{2}|Artifact - Equipment|||Equipped creature has nonbasic landwalk.$Equip {2}| +Walking Ballista|Final Fantasy Commander|371|R|{X}{X}|Artifact Creature - Construct|0|0|This creature enters with X +1/+1 counters on it.${4}: Put a +1/+1 counter on this creature.$Remove a +1/+1 counter from this creature: It deals 1 damage to any target.| +Wayfarer's Bauble|Final Fantasy Commander|372|C|{1}|Artifact|||{2}, {T}, Sacrifice this artifact: Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle.| +Arcane Sanctum|Final Fantasy Commander|373|U||Land|||This land enters tapped.${T}: Add {W}, {U}, or {B}.| +Ash Barrens|Final Fantasy Commander|374|C||Land|||{T}: Add {C}.$Basic landcycling {1}| +Battlefield Forge|Final Fantasy Commander|375|R||Land|||{T}: Add {C}.${T}: Add {R} or {W}. This land deals 1 damage to you.| +Bonders' Enclave|Final Fantasy Commander|376|R||Land|||{T}: Add {C}.${3}, {T}: Draw a card. Activate only if you control a creature with power 4 or greater.| +Brushland|Final Fantasy Commander|377|R||Land|||{T}: Add {C}.${T}: Add {G} or {W}. This land deals 1 damage to you.| +Canopy Vista|Final Fantasy Commander|378|R||Land - Forest Plains|||({T}: Add {G} or {W}.)$This land enters tapped unless you control two or more basic lands.| +Choked Estuary|Final Fantasy Commander|379|R||Land|||As this land enters, you may reveal an Island or Swamp card from your hand. If you don't, this land enters tapped.${T}: Add {U} or {B}.| +Cinder Glade|Final Fantasy Commander|380|R||Land - Mountain Forest|||({T}: Add {R} or {G}.)$This land enters tapped unless you control two or more basic lands.| +Clifftop Retreat|Final Fantasy Commander|381|R||Land|||This land enters tapped unless you control a Mountain or a Plains.${T}: Add {R} or {W}.| +Command Tower|Final Fantasy Commander|382|C||Land|||{T}: Add one mana of any color in your commander's color identity.| +Contaminated Aquifer|Final Fantasy Commander|383|C||Land - Island Swamp|||({T}: Add {U} or {B}.)$This land enters tapped.| +Darkwater Catacombs|Final Fantasy Commander|384|R||Land|||{1}, {T}: Add {U}{B}.| +Demolition Field|Final Fantasy Commander|385|U||Land|||{T}: Add {C}.${2}, {T}, Sacrifice this land: Destroy target nonbasic land an opponent controls. That land's controller may search their library for a basic land card, put it onto the battlefield, then shuffle. You may search your library for a basic land card, put it onto the battlefield, then shuffle.| +Desolate Mire|Final Fantasy Commander|386|R||Land|||{1}, {T}: Add {W}{B}.| +Dragonskull Summit|Final Fantasy Commander|387|R||Land|||This land enters tapped unless you control a Swamp or a Mountain.${T}: Add {B} or {R}.| +Drowned Catacomb|Final Fantasy Commander|388|R||Land|||This land enters tapped unless you control an Island or a Swamp.${T}: Add {U} or {B}.| +Evolving Wilds|Final Fantasy Commander|389|C||Land|||{T}, Sacrifice this land: Search your library for a basic land card, put it onto the battlefield tapped, then shuffle.| +Exotic Orchard|Final Fantasy Commander|390|R||Land|||{T}: Add one mana of any color that a land an opponent controls could produce.| +Fetid Heath|Final Fantasy Commander|391|R||Land|||{T}: Add {C}.${W/B}, {T}: Add {W}{W}, {W}{B}, or {B}{B}.| +Fire-Lit Thicket|Final Fantasy Commander|392|R||Land|||{T}: Add {C}.${R/G}, {T}: Add {R}{R}, {R}{G}, or {G}{G}.| +Flooded Grove|Final Fantasy Commander|393|R||Land|||{T}: Add {C}.${G/U}, {T}: Add {G}{G}, {G}{U}, or {U}{U}.| +Foreboding Ruins|Final Fantasy Commander|394|R||Land|||As this land enters, you may reveal a Swamp or Mountain card from your hand. If you don't, this land enters tapped.${T}: Add {B} or {R}.| +Forge of Heroes|Final Fantasy Commander|395|C||Land|||{T}: Add {C}.${T}: Choose target commander that entered this turn. Put a +1/+1 counter on it if it's a creature and a loyalty counter on it if it's a planeswalker.| +Fortified Village|Final Fantasy Commander|396|R||Land|||As this land enters, you may reveal a Forest or Plains card from your hand. If you don't, this land enters tapped.${T}: Add {G} or {W}.| +Furycalm Snarl|Final Fantasy Commander|397|R||Land|||As this land enters, you may reveal a Mountain or Plains card from your hand. If you don't, this land enters tapped.${T}: Add {R} or {W}.| +Game Trail|Final Fantasy Commander|398|R||Land|||As this land enters, you may reveal a Mountain or Forest card from your hand. If you don't, this land enters tapped.${T}: Add {R} or {G}.| +Geothermal Bog|Final Fantasy Commander|399|C||Land - Swamp Mountain|||({T}: Add {B} or {R}.)$This land enters tapped.| +Glacial Fortress|Final Fantasy Commander|400|R||Land|||This land enters tapped unless you control a Plains or an Island.${T}: Add {W} or {U}.| +Graven Cairns|Final Fantasy Commander|401|R||Land|||{T}: Add {C}.${B/R}, {T}: Add {B}{B}, {B}{R}, or {R}{R}.| +High Market|Final Fantasy Commander|402|R||Land|||{T}: Add {C}.${T}, Sacrifice a creature: You gain 1 life.| +Hinterland Harbor|Final Fantasy Commander|403|R||Land|||This land enters tapped unless you control a Forest or an Island.${T}: Add {G} or {U}.| +Idyllic Beachfront|Final Fantasy Commander|404|C||Land - Plains Island|||({T}: Add {W} or {U}.)$This land enters tapped.| +Isolated Chapel|Final Fantasy Commander|405|R||Land|||This land enters tapped unless you control a Plains or a Swamp.${T}: Add {W} or {B}.| +Jungle Shrine|Final Fantasy Commander|406|U||Land|||This land enters tapped.${T}: Add {R}, {G}, or {W}.| +Mossfire Valley|Final Fantasy Commander|407|R||Land|||{1}, {T}: Add {R}{G}.| +Nesting Grounds|Final Fantasy Commander|408|U||Land|||{T}: Add {C}.${1}, {T}: Move a counter from target permanent you control onto another target permanent. Activate only as a sorcery.| +Nomad Outpost|Final Fantasy Commander|409|U||Land|||This land enters tapped.${T}: Add {R}, {W}, or {B}.| +Overflowing Basin|Final Fantasy Commander|410|R||Land|||{1}, {T}: Add {G}{U}.| +Path of Ancestry|Final Fantasy Commander|411|C||Land|||This land enters tapped.${T}: Add one mana of any color in your commander's color identity. When that mana is spent to cast a creature spell that shares a creature type with your commander, scry 1.| +Port Town|Final Fantasy Commander|412|R||Land|||As this land enters, you may reveal a Plains or Island card from your hand. If you don't, this land enters tapped.${T}: Add {W} or {U}.| +Prairie Stream|Final Fantasy Commander|413|R||Land - Plains Island|||({T}: Add {W} or {U}.)$This land enters tapped unless you control two or more basic lands.| +Radiant Grove|Final Fantasy Commander|414|C||Land - Forest Plains|||({T}: Add {G} or {W}.)$This land enters tapped.| +Rogue's Passage|Final Fantasy Commander|415|U||Land|||{T}: Add {C}.${4}, {T}: Target creature can't be blocked this turn.| +Rootbound Crag|Final Fantasy Commander|416|R||Land|||This land enters tapped unless you control a Mountain or a Forest.${T}: Add {R} or {G}.| +Rugged Prairie|Final Fantasy Commander|417|R||Land|||{T}: Add {C}.${R/W}, {T}: Add {R}{R}, {R}{W}, or {W}{W}.| +Sacred Peaks|Final Fantasy Commander|418|C||Land - Mountain Plains|||({T}: Add {R} or {W}.)$This land enters tapped.| +Scavenger Grounds|Final Fantasy Commander|419|R||Land - Desert|||{T}: Add {C}.${2}, {T}, Sacrifice a Desert: Exile all graveyards.| +Seaside Citadel|Final Fantasy Commander|420|U||Land|||This land enters tapped.${T}: Add {G}, {W}, or {U}.| +Shadowblood Ridge|Final Fantasy Commander|421|R||Land|||{1}, {T}: Add {B}{R}.| +Shineshadow Snarl|Final Fantasy Commander|422|R||Land|||As this land enters, you may reveal a Plains or Swamp card from your hand. If you don't, this land enters tapped.${T}: Add {W} or {B}.| +Skycloud Expanse|Final Fantasy Commander|423|R||Land|||{1}, {T}: Add {W}{U}.| +Slayers' Stronghold|Final Fantasy Commander|424|R||Land|||{T}: Add {C}.${R}{W}, {T}: Target creature gets +2/+0 and gains vigilance and haste until end of turn.| +Smoldering Marsh|Final Fantasy Commander|425|R||Land - Swamp Mountain|||({T}: Add {B} or {R}.)$This land enters tapped unless you control two or more basic lands.| +Spire of Industry|Final Fantasy Commander|426|R||Land|||{T}: Add {C}.${T}, Pay 1 life: Add one mana of any color. Activate only if you control an artifact.| +Sulfurous Springs|Final Fantasy Commander|427|R||Land|||{T}: Add {C}.${T}: Add {B} or {R}. This land deals 1 damage to you.| +Sungrass Prairie|Final Fantasy Commander|428|R||Land|||{1}, {T}: Add {G}{W}.| +Sunken Hollow|Final Fantasy Commander|429|R||Land - Island Swamp|||({T}: Add {U} or {B}.)$This land enters tapped unless you control two or more basic lands.| +Sunken Ruins|Final Fantasy Commander|430|R||Land|||{T}: Add {C}.${U/B}, {T}: Add {U}{U}, {U}{B}, or {B}{B}.| +Sunlit Marsh|Final Fantasy Commander|431|C||Land - Plains Swamp|||({T}: Add {W} or {B}.)$This land enters tapped.| +Sunpetal Grove|Final Fantasy Commander|432|R||Land|||This land enters tapped unless you control a Forest or a Plains.${T}: Add {G} or {W}.| +Sunscorched Divide|Final Fantasy Commander|433|R||Land|||{1}, {T}: Add {R}{W}.| +Tangled Islet|Final Fantasy Commander|434|C||Land - Forest Island|||({T}: Add {G} or {U}.)$This land enters tapped.| +Temple of Enlightenment|Final Fantasy Commander|435|R||Land|||This land enters tapped.$When this land enters, scry 1.${T}: Add {W} or {U}.| +Temple of Mystery|Final Fantasy Commander|436|R||Land|||This land enters tapped.$When this land enters, scry 1.${T}: Add {G} or {U}.| +Temple of Plenty|Final Fantasy Commander|437|R||Land|||This land enters tapped.$When this land enters, scry 1.${T}: Add {G} or {W}.| +Temple of the False God|Final Fantasy Commander|438|U||Land|||{T}: Add {C}{C}. Activate only if you control five or more lands.| +Underground River|Final Fantasy Commander|439|R||Land|||{T}: Add {C}.${T}: Add {U} or {B}. This land deals 1 damage to you.| +Vineglimmer Snarl|Final Fantasy Commander|440|R||Land|||As this land enters, you may reveal a Forest or Island card from your hand. If you don't, this land enters tapped.${T}: Add {G} or {U}.| +Wooded Ridgeline|Final Fantasy Commander|441|C||Land - Mountain Forest|||({T}: Add {R} or {G}.)$This land enters tapped.| +Command Tower|Final Fantasy Commander|484|C||Land|||{T}: Add one mana of any color in your commander's color identity.| +Command Tower|Final Fantasy Commander|485|C||Land|||{T}: Add one mana of any color in your commander's color identity.| +Command Tower|Final Fantasy Commander|486|C||Land|||{T}: Add one mana of any color in your commander's color identity.| +Summon: Bahamut|Final Fantasy|1|M|{9}|Enchantment Creature - Saga Dragon|9|9|(As this Saga enters and after your draw step, add a lore counter. Sacrifice after IV.)$I, II -- Destroy up to one target nonland permanent.$III -- Draw two cards.$IV -- Mega Flare -- This creature deals damage equal to the total mana value of other permanents you control to each opponent.$Flying| +Ultima, Origin of Oblivion|Final Fantasy|2|R|{5}|Legendary Creature - God|4|4|Flying$Whenever Ultima attacks, put a blight counter on target land. For as long as that land has a blight counter on it, it loses all land types and abilities and has "{T}: Add {C}."$Whenever you tap a land for {C}, add an additional {C}.| +Aerith Gainsborough|Final Fantasy|4|R|{2}{W}|Legendary Creature - Human Cleric|2|2|Lifelink$Whenever you gain life, put a +1/+1 counter on Aerith Gainsborough.$When Aerith Gainsborough dies, put X +1/+1 counters on each legendary creature you control, where X is the number of +1/+1 counters on Aerith Gainsborough.| +Auron's Inspiration|Final Fantasy|8|U|{2}{W}|Instant|||Attacking creatures get +2/+0 until end of turn.$Flashback {2}{W}{W}| +Cloud, Midgar Mercenary|Final Fantasy|10|M|{W}{W}|Legendary Creature - Human Soldier Mercenary|2|1|When Cloud enters, search your library for an Equipment card, reveal it, put it into your hand, then shuffle.$As long as Cloud is equipped, if an ability of Cloud or an Equipment attached to it triggers, that ability triggers an additional time.| +Cloudbound Moogle|Final Fantasy|11|C|{3}{W}{W}|Creature - Moogle|2|3|Flying$When this creature enters, put a +1/+1 counter on target creature.$Plainscycling {2}| +Coeurl|Final Fantasy|12|C|{1}{W}|Creature - Cat Beast|2|2|{1}{W}, {T}: Tap target nonenchantment creature.| +The Crystal's Chosen|Final Fantasy|14|U|{5}{W}{W}|Sorcery|||Create four 1/1 colorless Hero creature tokens. Then put a +1/+1 counter on each creature you control.| +Dragoon's Lance|Final Fantasy|17|U|{1}{W}|Artifact - Equipment|||Job select$Equipped creature gets +1/+0 and is a Knight in addition to its other types.$During your turn, equipped creature has flying.$Gae Bolg -- Equip {4}| +Fate of the Sun-Cryst|Final Fantasy|19|C|{4}{W}|Instant|||This spell costs {2} less to cast if it targets a tapped creature.$Destroy target nonland permanent.| +Machinist's Arsenal|Final Fantasy|23|R|{4}{W}|Artifact - Equipment|||Job select$Equipped creature gets +2/+2 for each artifact you control and is an Artificer in addition to its other types.$Machina -- Equip {4}| +Moogles' Valor|Final Fantasy|27|R|{3}{W}{W}|Instant|||For each creature you control, create a 1/2 white Moogle creature token with lifelink. Then creatures you control gain indestructible until end of turn.| +Paladin's Arms|Final Fantasy|28|C|{2}{W}|Artifact - Equipment|||Job select$Equipped creature gets +2/+1, has ward {1}, and is a Knight in addition to its other types.$Lightbringer and Hero's Shield -- Equip {4}| +Phoenix Down|Final Fantasy|29|U|{W}|Artifact|||{1}{W}, {T}, Exile this artifact: Choose one--$* Return target creature card with mana value 4 or less from your graveyard to the battlefield tapped.$* Exile target Skeleton, Spirit, or Zombie.| Sidequest: Catch a Fish|Final Fantasy|31|U|{2}{W}|Enchantment|||At the beginning of your upkeep, look at the top card of your library. If it's an artifact or creature card, you may reveal it and put it into your hand. If you put a card into your hand this way, create a Food token and transform this enchantment.| Cooking Campsite|Final Fantasy|31|U||Land|||{T}: Add {W}.${3}, {T}, Sacrifice an artifact: Put a +1/+1 counter on each creature you control. Activate only as a sorcery.| Stiltzkin, Moogle Merchant|Final Fantasy|34|R|{W}|Legendary Creature - Moogle|1|2|Lifelink${2}, {T}: Target opponent gains control of another target permanent you control. If they do, you draw a card.| +Summon: Knights of Round|Final Fantasy|36|M|{6}{W}{W}|Enchantment Creature - Saga Knight|3|3|(As this Saga enters and after your draw step, add a lore counter. Sacrifice after V.)$I, II, III, IV -- Create three 2/2 white Knight creature tokens.$V -- Ultimate End -- Other creatures you control get +2/+2 until end of turn. Put an indestructible counter on each of them.$Indestructible| +Summon: Primal Garuda|Final Fantasy|37|U|{3}{W}|Enchantment Creature - Saga Harpy|3|3|(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I -- Aerial Blast -- This creature deals 4 damage to target tapped creature an opponent controls.$II, III -- Slipstream -- Another target creature you control gets +1/+0 and gains flying until end of turn.$Flying| +White Auracite|Final Fantasy|41|C|{2}{W}{W}|Artifact|||When this artifact enters, exile target nonland permanent an opponent controls until this artifact leaves the battlefield.${T}: Add {W}.| +White Mage's Staff|Final Fantasy|42|C|{1}{W}|Artifact - Equipment|||Job select$Equipped creature gets +1/+1, has "Whenever this creature attacks, you gain 1 life," and is a Cleric in addition to its other types.$Equip {3}| +You're Not Alone|Final Fantasy|44|C|{W}|Instant|||Target creature gets +2/+2 until end of turn. If you control three or more creatures, it gets +4/+4 until end of turn instead.| +Zack Fair|Final Fantasy|45|U|{W}|Legendary Creature - Human Soldier|0|1|Zack Fair enters with a +1/+1 counter on it.${1}, Sacrifice Zack Fair: Target creature you control gains indestructible until end of turn. Put Zack Fair's counters on that creature and attach an Equipment that was attached to Zack Fair to that creature.| +Astrologian's Planisphere|Final Fantasy|46|R|{1}{U}|Artifact - Equipment|||Job select$Equipped creature is a Wizard in addition to its other types and has "Whenever you cast a noncreature spell and whenever you draw your third card each turn, put a +1/+1 counter on this creature."$Diana -- Equip {2}| +Dragoon's Wyvern|Final Fantasy|49|C|{2}{U}|Creature - Drake|2|1|Flying$When this creature enters, create a 1/1 colorless Hero creature token.| +Dreams of Laguna|Final Fantasy|50|C|{1}{U}|Instant|||Surveil 1, then draw a card.$Flashback {3}{U}| +Ice Flan|Final Fantasy|55|C|{4}{U}{U}|Creature - Elemental Ooze|5|4|When this creature enters, tap target artifact or creature an opponent controls. Put a stun counter on it.$Islandcycling {2}| +Ice Magic|Final Fantasy|56|C|{1}{U}|Instant|||Tiered$* Blizzard -- {0} -- Return target creature to its owner's hand.$* Blizzara -- {2} -- Target creature's owner puts it on their choice of the top or bottom of their library.$* Blizzaga -- {5}{U} -- Target creature's owner shuffles it into their library.| +Jill, Shiva's Dominant|Final Fantasy|58|R|{2}{U}|Legendary Creature - Human Noble Warrior|2|2|When Jill enters, return up to one other target nonland permanent to its owner's hand.${3}{U}{U}, {T}: Exile Jill, then return it to the battlefield transformed under its owner's control. Activate only as a sorcery.| +Shiva, Warden of Ice|Final Fantasy|58|R||Legendary Enchantment Creature - Saga Elemental|4|5|(As this Saga enters and after your draw step, add a lore counter.)$I, II -- Mesmerize -- Target creature can't be blocked this turn.$III -- Cold Snap -- Tap all lands your opponents control. Exile Shiva, then return it to the battlefield.| +Matoya, Archon Elder|Final Fantasy|62|R|{2}{U}|Legendary Creature - Human Warlock|1|4|Whenever you scry or surveil, draw a card.| +The Prima Vista|Final Fantasy|64|U|{4}{U}|Legendary Artifact - Vehicle|5|3|Flying$Whenever you cast a noncreature spell, if at least four mana was spent to cast it, The Prima Vista becomes an artifact creature until end of turn.$Crew 2| +Quistis Trepe|Final Fantasy|66|U|{2}{U}|Legendary Creature - Human Wizard|2|2|Blue Magic -- When Quistis Trepe enters, you may cast target instant or sorcery card from a graveyard, and mana of any type can be spent to cast that spell. If that spell would be put into a graveyard, exile it instead.| +Relm's Sketching|Final Fantasy|67|U|{2}{U}{U}|Sorcery|||Create a token that's a copy of target artifact, creature, or land.| +Retrieve the Esper|Final Fantasy|68|C|{3}{U}|Sorcery|||Create a 3/3 blue Robot Warrior artifact creature token. Then if this spell was cast from a graveyard, put two +1/+1 counters on that token.$Flashback {5}{U}| +Sage's Nouliths|Final Fantasy|70|C|{1}{U}|Artifact - Equipment|||Job select$Equipped creature gets +1/+0, has "Whenever this creature attacks, untap target attacking creature," and is a Cleric in addition to its other types.$Hagneia -- Equip {3}| +Sahagin|Final Fantasy|71|C|{1}{U}|Creature - Merfolk Warrior|1|3|Whenever you cast a noncreature spell, if at least four mana was spent to cast it, put a +1/+1 counter on this creature and it can't be blocked this turn.| +Summon: Leviathan|Final Fantasy|77|R|{4}{U}{U}|Enchantment Creature - Saga Leviathan|6|6|I -- Return each creature that isn't a Kraken, Leviathan, Merfolk, Octopus, or Serpent to its owner's hand.$II, III -- Until end of turn, whenever a Kraken, Leviathan, Merfolk, Octopus, or Serpent attacks, draw a card.$Ward {2}| Summon: Shiva|Final Fantasy|78|U|{3}{U}{U}|Enchantment Creature - Saga Elemental|4|5|(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I, II -- Heavenly Strike -- Tap target creature an opponent controls. Put a stun counter on it.$III -- Diamond Dust -- Draw a card for each tapped creature your opponents control.| +Valkyrie Aerial Unit|Final Fantasy|84|U|{5}{U}{U}|Artifact Creature - Construct|5|4|Affinity for artifacts$Flying$When this creature enters, surveil 2.| +Y'shtola Rhul|Final Fantasy|86|M|{4}{U}{U}|Legendary Creature - Cat Druid|3|5|At the beginning of your end step, exile target creature you control, then return it to the battlefield under its owner's control. Then if it's the first end step of the turn, there is an additional end step after this step.| +Al Bhed Salvagers|Final Fantasy|88|U|{2}{B}|Creature - Human Artificer Warrior|2|3|Whenever this creature or another creature or artifact you control dies, target opponent loses 1 life and you gain 1 life.| +Ardyn, the Usurper|Final Fantasy|89|R|{5}{B}{B}{B}|Legendary Creature - Elder Human Noble|4|4|Demons you control have menace, lifelink, and haste.$Starscourge -- At the beginning of combat on your turn, exile up to one target creature card from a graveyard. If you exiled a card this way, create a token that's a copy of that card, except it's a 5/5 black Demon.| +Black Mage's Rod|Final Fantasy|90|C|{1}{B}|Artifact - Equipment|||Job select$Equipped creature gets +1/+0, has "Whenever you cast a noncreature spell, this creature deals 1 damage to each opponent," and is a Wizard in addition to its other types.$Equip {3}| Cecil, Dark Knight|Final Fantasy|91|R|{B}|Legendary Creature - Human Knight|2|3|Deathtouch$Darkness -- Whenever Cecil deals damage, you lose that much life. Then if your life total is less than or equal to half your starting life total, untap Cecil and transform it.| Cecil, Redeemed Paladin|Final Fantasy|91|R||Legendary Creature - Human Knight|4|4|Lifelink$Protect -- Whenever Cecil attacks, other attacking creatures gain indestructible until end of turn.| +Circle of Power|Final Fantasy|92|U|{3}{B}|Sorcery|||You draw two cards and you lose 2 life. Create a 0/1 black Wizard creature token with "Whenever you cast a noncreature spell, this token deals 1 damage to each opponent."$Wizards you control get +1/+0 and gain lifelink until end of turn.| +Dark Confidant|Final Fantasy|94|M|{1}{B}|Creature - Human Wizard|2|1|At the beginning of your upkeep, reveal the top card of your library and put that card into your hand. You lose life equal to its mana value.| +The Darkness Crystal|Final Fantasy|96|R|{2}{B}{B}|Legendary Artifact|||Black spells you cast cost {1} less to cast.$If a nontoken creature an opponent controls would die, instead exile it and you gain 2 life.${4}{B}{B}, {T}: Put target creature card exiled with The Darkness Crystal onto the battlefield tapped under your control with two additional +1/+1 counters on it.| +Evil Reawakened|Final Fantasy|98|U|{4}{B}|Sorcery|||Return target creature card from your graveyard to the battlefield with two additional +1/+1 counters on it.| +Fang, Fearless l'Cie|Final Fantasy|99|U|{2}{B}|Legendary Creature - Human Warrior|2|3|Whenever one or more cards leave your graveyard, you draw a card and you lose 1 life. This ability triggers only once each turn.$(Melds with Vanille, Cheerful l'Cie.)| +Ragnarok, Divine Deliverance|Final Fantasy|99b|U||Legendary Creature - Beast Avatar|7|6|Vigilance, menace, trample, reach, haste$When Ragnarok dies, destroy target permanent and return target nonlegendary permanent card from your graveyard to the battlefield.| +Jecht, Reluctant Guardian|Final Fantasy|104|R|{3}{B}|Legendary Creature - Human Warrior|4|3|Menace$Whenever Jecht deals combat damage to a player, you may exile it, then return it to the battlefield transformed under its owner's control.| +Braska's Final Aeon|Final Fantasy|104|R||Legendary Enchantment Creature - Saga Nightmare|7|7|(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I, II -- Jecht Beam -- Each opponent discards a card and you draw a card.$III -- Ultimate Jecht Shot -- Each opponent sacrifices two creatures of their choice.$Menace| +Kain, Traitorous Dragoon|Final Fantasy|105|R|{2}{B}|Legendary Creature - Human Knight|2|4|Jump -- During your turn, Kain has flying.$Whenever Kain deals combat damage to a player, that player gains control of Kain. If they do, you draw that many cards, create that many tapped Treasure tokens, then lose that much life.| +Malboro|Final Fantasy|106|C|{4}{B}{B}|Creature - Plant Horror|4|4|Bad Breath -- When this creature enters, each opponent discards a card, loses 2 life, and exiles the top three cards of their library.$Swampcycling {2}| +Namazu Trader|Final Fantasy|107|C|{3}{B}|Creature - Fish Citizen|3|4|When this creature enters, you lose 1 life and create a Treasure token.$Whenever this creature attacks, you may sacrifice another creature or artifact. If you do, surveil 2.| +Phantom Train|Final Fantasy|110|U|{3}{B}|Artifact - Vehicle|4|4|Trample$Sacrifice another artifact or creature: Put a +1/+1 counter on this Vehicle. It becomes a Spirit artifact creature in addition to its other types until end of turn.| +Poison the Waters|Final Fantasy|111|U|{1}{B}|Sorcery|||Choose one --$* All creatures get -1/-1 until end of turn.$* Target player reveals their hand. You choose an artifact or creature card from it. That player discards that card.| +Reno and Rude|Final Fantasy|113|U|{1}{B}|Legendary Creature - Human Assassin|2|1|Menace$Whenever Reno and Rude deals combat damage to a player, exile the top card of that player's library. Then you may sacrifice another creature or artifact. If you do, you may play the exiled card this turn, and mana of any type can be spent to cast it.| +Sephiroth, Fabled SOLDIER|Final Fantasy|115|M|{2}{B}|Legendary Creature - Human Avatar Soldier|3|3|Whenever Sephiroth enters or attacks, you may sacrifice another creature. If you do, draw a card.$Whenever another creature dies, target opponent loses 1 life and you gain 1 life. If this is the fourth time this ability has resolved this turn, transform Sephiroth.| +Sephiroth, One-Winged Angel|Final Fantasy|115|M||Legendary Creature - Angel Nightmare Avatar|5|5|Flying$Super Nova -- As this creature transforms into Sephiroth, One-Winged Angel, you get an emblem with "Whenever a creature dies, target opponent loses 1 life and you gain 1 life."$Whenever Sephiroth attacks, you may sacrifice any number of other creatures. If you do, draw that many cards.| +Sephiroth's Intervention|Final Fantasy|116|C|{3}{B}|Instant|||Destroy target creature. You gain 2 life.| +Shambling Cie'th|Final Fantasy|117|U|{2}{B}|Creature - Mutant Horror|3|3|This creature enters tapped.$Whenever you cast a noncreature spell, you may pay {B}. If you do, return this card from your graveyard to your hand.| +Shinra Reinforcements|Final Fantasy|118|C|{2}{B}|Creature - Human Soldier|2|3|When this creature enters, mill three cards and you gain 3 life.| +Summon: Anima|Final Fantasy|120|U|{4}{B}{B}|Enchantment Creature - Saga Horror|4|4|(As this Saga enters and after your draw step, add a lore counter. Sacrifice after IV.)$I, II, III -- Pain -- You draw a card and you lose 1 life.$IV -- Oblivion -- Each opponent sacrifices a creature of their choice and loses 3 life.$Menace| +Summon: Primal Odin|Final Fantasy|121|R|{4}{B}{B}|Enchantment Creature - Saga Knight|5|3|(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I -- Gungnir -- Destroy target creature an opponent controls.$II -- Zantetsuken -- This creature gains "Whenever this creature deals combat damage to a player, that player loses the game."$III -- Hall of Sorrow -- Draw two cards. Each player loses 2 life.| Tonberry|Final Fantasy|122|U|{B}|Creature - Salamander Horror|2|1|This creature enters tapped with a stun counter on it.$Chef's Knife -- During your turn, this creature has first strike and deathtouch.| +Undercity Dire Rat|Final Fantasy|123|C|{1}{B}|Creature - Rat|2|2|Rat Tail -- When this creature dies, create a Treasure token.| +Vincent Valentine|Final Fantasy|125|R|{2}{B}{B}|Legendary Creature - Assassin|2|2|Whenever a creature an opponent controls dies, put a number of +1/+1 counters on Vincent Valentine equal to that creature's power.$Whenever Vincent Valentine attacks, you may transform it.| +Galian Beast|Final Fantasy|125|R||Legendary Creature - Werewolf Beast|3|2|Trample, lifelink$When Galian Beast dies, return it to the battlefield tapped.| +Zenos yae Galvus|Final Fantasy|127|R|{3}{B}{B}|Legendary Creature - Human Noble Warrior|4|4|My First Friend -- When Zenos yae Galvus enters, choose a creature an opponent controls. Until end of turn, creatures other than Zenos yae Galvus and the chosen creature get -2/-2.$When the chosen creature leaves the battlefield, transform Zenos yae Galvus.| +Shinryu, Transcendent Rival|Final Fantasy|127|R||Legendary Creature - Dragon|8|8|Flying$As this creature transforms into Shinryu, choose an opponent.$Burning Chains -- When the chosen player loses the game, you win the game.| +Zodiark, Umbral God|Final Fantasy|128|R|{B}{B}{B}{B}{B}|Legendary Creature - God|5|5|Indestructible$When Zodiark enters, each player sacrifices half the non-God creatures they control of their choice, rounded down.$Whenever a player sacrifices another creature, put a +1/+1 counter on Zodiark.| +Barret Wallace|Final Fantasy|129|U|{3}{R}|Legendary Creature - Human Rebel|4|4|Reach$Whenever Barret Wallace attacks, it deals damage equal to the number of equipped creatures you control to defending player.| +Clive, Ifrit's Dominant|Final Fantasy|133|M|{4}{R}{R}|Legendary Creature - Human Noble Warrior|5|5|When Clive enters, you may discard your hand, then draw cards equal to your devotion to red.${4}{R}{R}, {T}: Exile Clive, then return it to the battlefield transformed under its owner's control. Activate only as a sorcery.| +Ifrit, Warden of Inferno|Final Fantasy|133|M||Legendary Enchantment Creature - Saga Demon|9|9|(As this Saga enters and after your draw step, add a lore counter.)$I -- Lunge -- Ifrit fights up to one other target creature.$II, III -- Brimstone -- Add {R}{R}{R}{R}. If Ifrit has three or more lore counters on it, exile it, then return it to the battlefield| +Fire Magic|Final Fantasy|136|U|{R}|Instant|||Tiered$* Fire -- {0} -- Fire Magic deals 1 damage to each creature.$* Fira -- {2} -- Fire Magic deals 2 damage to each creature.$* Firaga -- {5} -- Fire Magic deals 3 damage to each creature.| +Firion, Wild Rose Warrior|Final Fantasy|137|R|{2}{R}|Legendary Creature - Human Rebel Warrior|3|3|Equipped creatures you control have haste.$Whenever a nontoken Equipment you control enters, create a token that's a copy of it, except it has "This Equipment's equip abilities cost {2} less to activate." Sacrifice that token at the beginning of the next upkeep.| +Freya Crescent|Final Fantasy|138|U|{R}|Legendary Creature - Rat Knight|1|1|Jump -- During your turn, Freya Crescent has flying.${T}: Add {R}. Spend this mana only to cast an Equipment spell or activate an equip ability.| +Gilgamesh, Master-at-Arms|Final Fantasy|139|R|{4}{R}{R}|Legendary Creature - Human Samurai|6|6|Whenever Gilgamesh enters or attacks, look at the top six cards of your library. You may put any number of Equipment cards from among them onto the battlefield. Put the rest on the bottom of your library in a random order. When you put one or more Equipment onto the battlefield this way, you may attach one of them to a Samurai you control.| +Item Shopkeep|Final Fantasy|142|C|{1}{R}|Creature - Human Citizen|2|2|Whenever you attack, target attacking equipped creature gains menace until end of turn.| +Laughing Mad|Final Fantasy|143|C|{2}{R}|Instant|||As an additional cost to cast this spell, discard a card.$Draw two cards.$Flashback {3}{R}| +Nibelheim Aflame|Final Fantasy|146|M|{2}{R}{R}|Sorcery|||Choose target creature you control. It deals damage equal to its power to each other creature. If this spell was cast from a graveyard, discard your hand and draw four cards.$Flashback {5}{R}{R}| +Samurai's Katana|Final Fantasy|154|U|{2}{R}|Artifact - Equipment|||Job select$Equipped creature gets +2/+2, has trample and haste, and is a Samurai in addition to its other types.$Murasame -- Equip {5}| +Self-Destruct|Final Fantasy|157|U|{1}{R}|Instant|||Target creature you control deals X damage to any other target and X damage to itself, where X is its power.| +Summon: Esper Ramuh|Final Fantasy|161|U|{2}{R}{R}|Enchantment Creature - Saga Wizard|3|3|(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I -- Judgment Bolt -- This creature deals damage equal to the number of noncreature, nonland cards in your graveyard to target creature an opponent controls.$II, III -- Wizards you control get +1/+0 until end of turn.| +Suplex|Final Fantasy|164|C|{1}{R}|Sorcery|||Choose one --$* Suplex deals 3 damage to target creature. If that creature would die this turn, exile it instead.$* Exile target artifact.| +Triple Triad|Final Fantasy|166|R|{3}{R}{R}{R}|Enchantment|||At the beginning of your upkeep, each player exiles the top card of their library. Until end of turn, you may play the card you own exiled this way and each other card exiled this way with lesser mana value than it without paying their mana costs.| +Warrior's Sword|Final Fantasy|169|C|{3}{R}|Artifact - Equipment|||Job select$Equipped creature gets +3/+2 and is a Warrior in addition to its other types.$Equip {5}| Zell Dincht|Final Fantasy|170|R|{2}{R}|Legendary Creature - Human Monk|0|3|You may play an additional land on each of your turns.$Zell Dincht gets +1/+0 for each land you control.$At the beginning of your end step, return a land you control to its owner's hand.| +Bartz and Boko|Final Fantasy|175|R|{3}{G}{G}|Legendary Creature - Human Bird|4|3|Affinity for Birds$When Bartz and Boko enters, each other Bird you control deals damage equal to its power to target creature an opponent controls.| +Commune with Beavers|Final Fantasy|182|C|{G}|Sorcery|||Look at the top three cards of your library. You may reveal an artifact, creature, or land card from among them and put it into your hand. Put the rest on the bottom of your library in any order.| +Esper Origins|Final Fantasy|185|R|{1}{G}|Sorcery|||Surveil 2. You gain 2 life. If this spell was cast from a graveyard, exile it, then put it onto the battlefield transformed under its owner's control with a finality counter on it.$Flashback {3}{G}| +Summon: Esper Maduin|Final Fantasy|185|R||Enchantment Creature - Saga Elemental|4|4|(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I -- Reveal the top card of your library. If it's a permanent card, put it into your hand.$II -- Add {G}{G}.$III -- Other creatures you control get +2/+2 and gain trample until end of turn.| +Galuf's Final Act|Final Fantasy|186|U|{1}{G}|Instant|||Until end of turn, target creature gets +1/+0 and gains "When this creature dies, put a number of +1/+1 counters equal to its power on up to one target creature."| Jumbo Cactuar|Final Fantasy|191|R|{5}{G}{G}|Creature - Plant|1|7|10,000 Needles -- Whenever this creature attacks, it gets +9999/+0 until end of turn.| +Quina, Qu Gourmet|Final Fantasy|194|U|{2}{G}|Legendary Creature - Qu|2|3|If one or more tokens would be created under your control, those tokens plus a 1/1 green Frog creature token are created instead.${2}, Sacrifice a Frog: Put a +1/+1 counter on Quina.| +A Realm Reborn|Final Fantasy|196|R|{4}{G}{G}|Enchantment|||Other permanents you control have "{T}: Add one mana of any color."| +Sazh Katzroy|Final Fantasy|199|R|{3}{G}|Legendary Creature - Human Pilot|3|3|When Sazh Katzroy enters, you may search your library for a Bird or basic land card, reveal it, put it into your hand, then shuffle.$Whenever Sazh Katzroy attacks, put a +1/+1 counter on target creature, then double the number of +1/+1 counters on that creature.| Sazh's Chocobo|Final Fantasy|200|U|{G}|Creature - Bird|0|1|Landfall -- Whenever a land you control enters, put a +1/+1 counter on this creature.| +Summon: Fenrir|Final Fantasy|203|U|{2}{G}|Enchantment Creature - Saga Wolf|3|2|(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I -- Crescent Fang -- Search your library for a basic land card, put it onto the battlefield tapped, then shuffle.$II -- Heavenward Howl -- When you next cast a creature spell this turn, that creature enters with an additional +1/+1 counter on it.$III -- Ecliptic Growl -- Draw a card if you control the creature with the greatest power or tied for the greatest power.| +Summoner's Grimoire|Final Fantasy|205|R|{3}{G}|Artifact - Equipment|||Job select$Equipped creature is a Shaman in addition to its other types and has "Whenever this creature attacks, you may put a creature card from your hand onto the battlefield. If that card is an enchantment card, it enters tapped and attacking."$Abraxas -- Equip {3}| +Tifa Lockhart|Final Fantasy|206|R|{1}{G}|Legendary Creature - Human Monk|1|2|Trample$Landfall -- Whenever a land you control enters, double Tifa Lockhart's power until end of turn.| +Town Greeter|Final Fantasy|209|C|{1}{G}|Creature - Human Citizen|1|1|When this creature enters, mill four cards. You may put a land card from among them into your hand. If you put a Town card into your hand this way, you gain 2 life.| +Traveling Chocobo|Final Fantasy|210|M|{2}{G}|Creature - Bird|3|2|You may look at the top card of your library any time.$You may play lands and cast Bird spells from the top of your library.$If a land or Bird you control entering the battlefield causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time.| +Vanille, Cheerful l'Cie|Final Fantasy|211|U|{3}{G}|Legendary Creature - Human Cleric|3|2|When Vanille enters, mill two cards, then return a permanent card from your graveyard to your hand.$At the beginning of your first main phase, if you both own and control Vanille and a creature named Fang, Fearless l'Cie, you may pay {3}{B}{G}. If you do, exile them, then meld them into Ragnarok, Divine Deliverance.| +Absolute Virtue|Final Fantasy|212|M|{6}{W}{U}|Legendary Creature - Avatar Warrior|8|8|This spell can't be countered.$Flying$You have protection from each of your opponents.| +Balthier and Fran|Final Fantasy|213|R|{1}{R}{G}|Legendary Creature - Human Rabbit|4|3|Reach$Vehicles you control get +1/+1 and have vigilance and reach.$Whenever a Vehicle crewed by Balthier and Fran this turn attacks, if it's the first combat phase of the turn, you may pay {1}{R}{G}. If you do, after this phase, there is an additional combat phase.| +Choco, Seeker of Paradise|Final Fantasy|215|R|{1}{G}{W}{U}|Legendary Creature - Bird|3|5|Whenever one or more Birds you control attack, look at that many cards from the top of your library. You may put one of them into your hand. Then put any number of land cards from among them onto the battlefield tapped and the rest into your graveyard.$Landfall -- Whenever a land you control enters, Choco gets +1/+0 until end of turn.| +Cid, Timeless Artificer|Final Fantasy|216|U|{2}{W}{U}|Legendary Creature - Human Artificer|4|4|Artifact creatures and Heroes you control get +1/+1 for each Artificer you control and each Artificer card in your graveyard.$A deck can have any number of cards named Cid, Timeless Artificer.$Cycling {W}{U}| Emet-Selch, Unsundered|Final Fantasy|218|M|{1}{U}{B}|Legendary Creature - Elder Wizard|2|4|Vigilance$Whenever Emet-Selch enters or attacks, draw a card, then discard a card.$At the beginning of your upkeep, if there are fourteen or more cards in your graveyard, you may transform Emet-Selch.| Hades, Sorcerer of Eld|Final Fantasy|218|M||Legendary Creature - Avatar|6|6|Vigilance$Echo of the Lost -- During your turn, you may play cards from your graveyard.$If a card or token would be put into your graveyard from anywhere, exile it instead.| +The Emperor of Palamecia|Final Fantasy|219|U|{U}{R}|Legendary Creature - Human Noble Wizard|2|2|{T}: Add {U} or {R}. Spend this mana only to cast a noncreature spell.$Whenever you cast a noncreature spell, if at least four mana was spent to cast it, put a +1/+1 counter on The Emperor of Palamecia. Then if it has three or more +1/+1 counters on it, transform it.| +The Lord Master of Hell|Final Fantasy|219|U||Legendary Creature - Demon Noble Wizard|3|3|Starfall -- Whenever The Lord Master of Hell attacks, it deals X damage to each opponent, where X is the number of noncreature, nonland cards in your graveyard.| Garland, Knight of Cornelia|Final Fantasy|221|U|{B}{R}|Legendary Creature - Human Knight|3|2|Whenever you cast a noncreature spell, surveil 1.${3}{B}{B}{R}{R}: Return this card from your graveyard to the battlefield transformed. Activate only as a sorcery.| Chaos, the Endless|Final Fantasy|221|U||Legendary Creature - Demon|5|5|Flying$When Chaos dies, put it on the bottom of its owner's library.| Gladiolus Amicitia|Final Fantasy|224|U|{4}{R}{G}|Legendary Creature - Human Warrior|6|6|When Gladiolus Amicitia enters, search your library for a land card, put it onto the battlefield tapped, then shuffle.$Landfall -- Whenever a land you control enters, another target creature you control gets +2/+2 and gains trample until end of turn.| +Kefka, Court Mage|Final Fantasy|231|M|{2}{U}{B}{R}|Legendary Creature - Human Wizard|4|5|Whenever Kefka enters or attacks, each player discards a card. Then you draw a card for each card type among cards discarded this way.${8}: Each opponent sacrifices a permanent of their choice. Transform Kefka. Activate only as a sorcery.| +Kefka, Ruler of Ruin|Final Fantasy|231|M||Legendary Creature - Avatar Wizard|5|7|Flying$Whenever an opponent loses life during your turn, you draw that many cards.| +Kuja, Genome Sorcerer|Final Fantasy|232|R|{2}{B}{R}|Legendary Creature - Human Mutant Wizard|3|4|At the beginning of your end step, create a tapped 0/1 black Wizard creature token with "Whenever you cast a noncreature spell, this token deals 1 damage to each opponent." Then if you control four or more Wizards, transform Kuja.| +Trance Kuja, Fate Defied|Final Fantasy|232|R||Legendary Creature - Avatar Wizard|4|6|Flame Star -- If a Wizard you control would deal damage to a permanent or player, it deals double that damage instead.| +Lightning, Army of One|Final Fantasy|233|M|{1}{R}{W}|Legendary Creature - Human Soldier|3|2|First strike, trample, lifelink$Stagger -- Whenever Lightning deals combat damage to a player, until your next turn, if a source would deal damage to that player or a permanent that player controls, it deals double that damage instead.| +Noctis, Prince of Lucis|Final Fantasy|235|R|{1}{W}{U}{B}|Legendary Creature - Human Noble|4|3|Lifelink$You may cast artifact spells from your graveyard by paying 3 life in addition to paying their other costs. If you cast a spell this way, that artifact enters with a finality counter on it.| +Rinoa Heartilly|Final Fantasy|237|U|{3}{G}{W}|Legendary Creature - Human Rebel Warlock|4|4|When Rinoa Heartilly enters, create Angelo, a legendary 1/1 green and white Dog creature token.$Angelo Cannon -- Whenever Rinoa Heartilly attacks, another target creature you control gets +1/+1 until end of turn for each creature you control.| +Serah Farron|Final Fantasy|240|R|{1}{G}{W}|Legendary Creature - Human Citizen|2|2|The first legendary creature spell you cast each turn costs {2} less to cast.$At the beginning of combat on your turn, if you control two or more other legendary creatures, you may transform Serah Farron.| +Crystallized Serah|Final Fantasy|240|R||Legendary Artifact|||The first legendary creature spell you cast each turn costs {2} less to cast.$Legendary creatures you control get +2/+2.| +Shantotto, Tactician Magician|Final Fantasy|241|U|{1}{U}{R}|Legendary Creature - Dwarf Wizard|0|4|Whenever you cast a noncreature spell, Shantotto gets +X/+0 until end of turn, where X is the amount of mana spent to cast that spell. If X is 4 or more, draw a card.| Sin, Spira's Punishment|Final Fantasy|242|R|{4}{B}{G}{U}|Legendary Creature - Leviathan Avatar|7|7|Flying$Whenever Sin enters or attacks, exile a permanent card from your graveyard at random, then create a tapped token that's a copy of that card. If the exiled card is a land card, repeat this process.| +Squall, SeeD Mercenary|Final Fantasy|243|R|{2}{W}{B}|Legendary Creature - Human Knight Mercenary|3|4|Rough Divide -- Whenever a creature you control attacks alone, it gains double strike until end of turn.$Whenever Squall deals combat damage to a player, return target permanent card with mana value 3 or less from your graveyard to the battlefield.| +Terra, Magical Adept|Final Fantasy|245|M|{1}{R}{G}|Legendary Creature - Human Wizard Warrior|4|2|When Terra enters, mill five cards. Put up to one enchantment card milled this way into your hand.$Trance -- {4}{R}{G}, {T}: Exile Terra, then return it to the battlefield transformed under its owner's control. Activate only as a sorcery.| +Esper Terra|Final Fantasy|245|M||Legendary Enchantment Creature - Saga Wizard|6|6|(As this Saga enters and after your draw step, add a lore counter.)$I, II, III -- Create a token that's a copy of target nonlegendary enchantment you control. It gains haste. If it's a Saga, put up to three lore counters on it. Sacrifice it at the beginning of your next end step.$IV -- Add {W}{W}, {U}{U}, {B}{B}, {R}{R}, and {G}{G}. Exile Esper Terra, then return it to the battlefield.$Flying| +Ultimecia, Time Sorceress|Final Fantasy|247|U|{3}{U}{B}|Legendary Creature - Human Warlock|4|5|Whenever Ultimecia enters or attacks, surveil 2.$At the beginning of your end step, you may pay {4}{U}{U}{B}{B} and exile eight cards from your graveyard. If you do, transform Ultimecia.| +Ultimecia, Omnipotent|Final Fantasy|247|U||Legendary Creature - Nightmare Warlock|7|7|Menace$Time Compression -- When this creature transforms into Ultimecia, Omnipotent, take an extra turn after this one.| +Vivi Ornitier|Final Fantasy|248|M|{1}{U}{R}|Legendary Creature - Wizard|0|3|{0}: Add X mana in any combination of {U} and/or {R}, where X is Vivi Ornitier's power. Activate only during your turn and only once each turn.$Whenever you cast a noncreature spell, put a +1/+1 counter on Vivi Ornitier and it deals 1 damage to each opponent.| +Yuna, Hope of Spira|Final Fantasy|250|M|{3}{G}{W}|Legendary Creature - Human Cleric|3|5|During your turn, Yuna and enchantment creatures you control have trample, lifelink, and ward {2}.$At the beginning of your end step, return up to one target enchantment card from your graveyard to the battlefield with a finality counter on it.| +Zidane, Tantalus Thief|Final Fantasy|251|U|{3}{R}{W}|Legendary Creature - Human Mutant Scout|3|3|When Zidane enters, gain control of target creature an opponent controls until end of turn. Untap that creature. It gains lifelink and haste until end of turn.$Whenever an opponent gains control of a permanent you control, create a Treasure token.| +Buster Sword|Final Fantasy|255|M|{3}|Artifact - Equipment|||Equipped creature gets +3/+2.$Whenever equipped creature deals combat damage to a player, draw a card, then you may cast a spell from your hand with mana value less than or equal to that damage without paying its mana cost.$Equip {2}| +Instant Ramen|Final Fantasy|259|C|{2}|Artifact - Food|||Flash$When this artifact enters, draw a card.${2}, {T}, Sacrifice this artifact: You gain 3 life.| +PuPu UFO|Final Fantasy|266|U|{2}|Artifact Creature - Construct Alien|0|4|Flying${T}: You may put a land card from your hand onto the battlefield.${3}: Until end of turn, this creature's base power becomes equal to the number of Towns you control.| +Adventurer's Inn|Final Fantasy|271|C||Land - Town|||When this land enters, you gain 2 life.${T}: Add {C}.| +Capital City|Final Fantasy|274|U||Land - Town|||{T}: Add {C}.${1}, {T}: Add one mana of any color.$Cycling {2}| +Ishgard, the Holy See|Final Fantasy|283|R|{3}{W}{W}|Land - Town|||This land enters tapped.${T}: Add {W}.| +Faith & Grief|Final Fantasy|283|R||Sorcery - Adventure|||Return up to two target artifact and/or enchantment cards from your graveyard to your hand.| +Jidoor, Aristocratic Capital|Final Fantasy|284|R|{4}{U}{U}|Land - Town|||This land enters tapped.${T}: Add {U}.| +Overture|Final Fantasy|284|R||Sorcery - Adventure|||Target opponent mills half their library, rounded down.| +Rabanastre, Royal City|Final Fantasy|287|C||Land - Town|||This land enters tapped.${T}: Add {R} or {W}.| +Starting Town|Final Fantasy|289|R||Land - Town|||This land enters tapped unless it's your first, second, or third turn of the game.${T}: Add {C}.${T}, Pay 1 life: Add one mana of any color.| +Zanarkand, Ancient Metropolis|Final Fantasy|293|R|{4}{G}{G}|Land - Town|||This land enters tapped.${T}: Add {G}.| +Lasting Fayth|Final Fantasy|293|R||Sorcery - Adventure|||Create a 1/1 colorless Hero creature token. Put a +1/+1 counter on it for each land you control.| +Plains|Final Fantasy|294|C||Basic Land - Plains|||({T}: Add {W}.)| +Plains|Final Fantasy|295|C||Basic Land - Plains|||({T}: Add {W}.)| +Plains|Final Fantasy|296|C||Basic Land - Plains|||({T}: Add {W}.)| +Island|Final Fantasy|297|C||Basic Land - Island|||({T}: Add {U}.)| +Island|Final Fantasy|298|C||Basic Land - Island|||({T}: Add {U}.)| +Island|Final Fantasy|299|C||Basic Land - Island|||({T}: Add {U}.)| +Swamp|Final Fantasy|300|C||Basic Land - Swamp|||({T}: Add {B}.)| +Swamp|Final Fantasy|301|C||Basic Land - Swamp|||({T}: Add {B}.)| +Swamp|Final Fantasy|302|C||Basic Land - Swamp|||({T}: Add {B}.)| +Mountain|Final Fantasy|303|C||Basic Land - Mountain|||({T}: Add {R}.)| +Mountain|Final Fantasy|304|C||Basic Land - Mountain|||({T}: Add {R}.)| +Mountain|Final Fantasy|305|C||Basic Land - Mountain|||({T}: Add {R}.)| +Forest|Final Fantasy|306|C||Basic Land - Forest|||({T}: Add {G}.)| +Forest|Final Fantasy|307|C||Basic Land - Forest|||({T}: Add {G}.)| +Forest|Final Fantasy|308|C||Basic Land - Forest|||({T}: Add {G}.)| +Wastes|Final Fantasy|309|C||Basic Land|||{T}: Add {C}.| +Ishgard, the Holy See|Final Fantasy|310|R|{3}{W}{W}|Land - Town|||This land enters tapped.${T}: Add {W}.| +Faith & Grief|Final Fantasy|310|R||Sorcery - Adventure|||Return up to two target artifact and/or enchantment cards from your graveyard to your hand.| +Jidoor, Aristocratic Capital|Final Fantasy|311|R|{4}{U}{U}|Land - Town|||This land enters tapped.${T}: Add {U}.| +Overture|Final Fantasy|311|R||Sorcery - Adventure|||Target opponent mills half their library, rounded down.| +Zanarkand, Ancient Metropolis|Final Fantasy|314|R|{4}{G}{G}|Land - Town|||This land enters tapped.${T}: Add {G}.| +Lasting Fayth|Final Fantasy|314|R||Sorcery - Adventure|||Create a 1/1 colorless Hero creature token. Put a +1/+1 counter on it for each land you control.| +Ardyn, the Usurper|Final Fantasy|315|R|{5}{B}{B}{B}|Legendary Creature - Elder Human Noble|4|4|Demons you control have menace, lifelink, and haste.$Starscourge -- At the beginning of combat on your turn, exile up to one target creature card from a graveyard. If you exiled a card this way, create a token that's a copy of that card, except it's a 5/5 black Demon.| +Kain, Traitorous Dragoon|Final Fantasy|316|R|{2}{B}|Legendary Creature - Human Knight|2|4|Jump -- During your turn, Kain has flying.$Whenever Kain deals combat damage to a player, that player gains control of Kain. If they do, you draw that many cards, create that many tapped Treasure tokens, then lose that much life.| +Sephiroth, Fabled SOLDIER|Final Fantasy|317|M|{2}{B}|Legendary Creature - Human Avatar Soldier|3|3|Whenever Sephiroth enters or attacks, you may sacrifice another creature. If you do, draw a card.$Whenever another creature dies, target opponent loses 1 life and you gain 1 life. If this is the fourth time this ability has resolved this turn, transform Sephiroth.| +Sephiroth, One-Winged Angel|Final Fantasy|317|M||Legendary Creature - Angel Nightmare Avatar|5|5|Flying$Super Nova -- As this creature transforms into Sephiroth, One-Winged Angel, you get an emblem with "Whenever a creature dies, target opponent loses 1 life and you gain 1 life."$Whenever Sephiroth attacks, you may sacrifice any number of other creatures. If you do, draw that many cards.| +Clive, Ifrit's Dominant|Final Fantasy|318|M|{4}{R}{R}|Legendary Creature - Human Noble Warrior|5|5|When Clive enters, you may discard your hand, then draw cards equal to your devotion to red.${4}{R}{R}, {T}: Exile Clive, then return it to the battlefield transformed under its owner's control. Activate only as a sorcery.| +Ifrit, Warden of Inferno|Final Fantasy|318|M||Legendary Enchantment Creature - Saga Demon|9|9|(As this Saga enters and after your draw step, add a lore counter.)$I -- Lunge -- Ifrit fights up to one other target creature.$II, III -- Brimstone -- Add {R}{R}{R}{R}. If Ifrit has three or more lore counters on it, exile it, then return it to the battlefield| +Balthier and Fran|Final Fantasy|319|R|{1}{R}{G}|Legendary Creature - Human Rabbit|4|3|Reach$Vehicles you control get +1/+1 and have vigilance and reach.$Whenever a Vehicle crewed by Balthier and Fran this turn attacks, if it's the first combat phase of the turn, you may pay {1}{R}{G}. If you do, after this phase, there is an additional combat phase.| +Lightning, Army of One|Final Fantasy|320|M|{1}{R}{W}|Legendary Creature - Human Soldier|3|2|First strike, trample, lifelink$Stagger -- Whenever Lightning deals combat damage to a player, until your next turn, if a source would deal damage to that player or a permanent that player controls, it deals double that damage instead.| +Vivi Ornitier|Final Fantasy|321|M|{1}{U}{R}|Legendary Creature - Wizard|0|3|{0}: Add X mana in any combination of {U} and/or {R}, where X is Vivi Ornitier's power. Activate only during your turn and only once each turn.$Whenever you cast a noncreature spell, put a +1/+1 counter on Vivi Ornitier and it deals 1 damage to each opponent.| +Kefka, Court Mage|Final Fantasy|322|M|{2}{U}{B}{R}|Legendary Creature - Human Wizard|4|5|Whenever Kefka enters or attacks, each player discards a card. Then you draw a card for each card type among cards discarded this way.${8}: Each opponent sacrifices a permanent of their choice. Transform Kefka. Activate only as a sorcery.| +Kefka, Ruler of Ruin|Final Fantasy|322|M||Legendary Creature - Avatar Wizard|5|7|Flying$Whenever an opponent loses life during your turn, you draw that many cards.| +Terra, Magical Adept|Final Fantasy|323|M|{1}{R}{G}|Legendary Creature - Human Wizard Warrior|4|2|When Terra enters, mill five cards. Put up to one enchantment card milled this way into your hand.$Trance -- {4}{R}{G}, {T}: Exile Terra, then return it to the battlefield transformed under its owner's control. Activate only as a sorcery.| +Esper Terra|Final Fantasy|323|M||Legendary Enchantment Creature - Saga Wizard|6|6|(As this Saga enters and after your draw step, add a lore counter.)$I, II, III -- Create a token that's a copy of target nonlegendary enchantment you control. It gains haste. If it's a Saga, put up to three lore counters on it. Sacrifice it at the beginning of your next end step.$IV -- Add {W}{W}, {U}{U}, {B}{B}, {R}{R}, and {G}{G}. Exile Esper Terra, then return it to the battlefield.$Flying| +Ultima, Origin of Oblivion|Final Fantasy|324|R|{5}|Legendary Creature - God|4|4|Flying$Whenever Ultima attacks, put a blight counter on target land. For as long as that land has a blight counter on it, it loses all land types and abilities and has "{T}: Add {C}."$Whenever you tap a land for {C}, add an additional {C}.| +Moogles' Valor|Final Fantasy|326|R|{3}{W}{W}|Instant|||For each creature you control, create a 1/2 white Moogle creature token with lifelink. Then creatures you control gain indestructible until end of turn.| +Stiltzkin, Moogle Merchant|Final Fantasy|327|R|{W}|Legendary Creature - Moogle|1|2|Lifelink${2}, {T}: Target opponent gains control of another target permanent you control. If they do, you draw a card.| +Dark Confidant|Final Fantasy|334|M|{1}{B}|Creature - Human Wizard|2|1|At the beginning of your upkeep, reveal the top card of your library and put that card into your hand. You lose life equal to its mana value.| +The Darkness Crystal|Final Fantasy|335|R|{2}{B}{B}|Legendary Artifact|||Black spells you cast cost {1} less to cast.$If a nontoken creature an opponent controls would die, instead exile it and you gain 2 life.${4}{B}{B}, {T}: Put target creature card exiled with The Darkness Crystal onto the battlefield tapped under your control with two additional +1/+1 counters on it.| +Zodiark, Umbral God|Final Fantasy|336|R|{B}{B}{B}{B}{B}|Legendary Creature - God|5|5|Indestructible$When Zodiark enters, each player sacrifices half the non-God creatures they control of their choice, rounded down.$Whenever a player sacrifices another creature, put a +1/+1 counter on Zodiark.| +Gilgamesh, Master-at-Arms|Final Fantasy|338|R|{4}{R}{R}|Legendary Creature - Human Samurai|6|6|Whenever Gilgamesh enters or attacks, look at the top six cards of your library. You may put any number of Equipment cards from among them onto the battlefield. Put the rest on the bottom of your library in a random order. When you put one or more Equipment onto the battlefield this way, you may attach one of them to a Samurai you control.| +Nibelheim Aflame|Final Fantasy|339|M|{2}{R}{R}|Sorcery|||Choose target creature you control. It deals damage equal to its power to each other creature. If this spell was cast from a graveyard, discard your hand and draw four cards.$Flashback {5}{R}{R}| +Triple Triad|Final Fantasy|340|R|{3}{R}{R}{R}|Enchantment|||At the beginning of your upkeep, each player exiles the top card of their library. Until end of turn, you may play the card you own exiled this way and each other card exiled this way with lesser mana value than it without paying their mana costs.| +Jumbo Cactuar|Final Fantasy|343|R|{5}{G}{G}|Creature - Plant|1|7|10,000 Needles -- Whenever this creature attacks, it gets +9999/+0 until end of turn.| +A Realm Reborn|Final Fantasy|344|R|{4}{G}{G}|Enchantment|||Other permanents you control have "{T}: Add one mana of any color."| +Sin, Spira's Punishment|Final Fantasy|348|R|{4}{B}{G}{U}|Legendary Creature - Leviathan Avatar|7|7|Flying$Whenever Sin enters or attacks, exile a permanent card from your graveyard at random, then create a tapped token that's a copy of that card. If the exiled card is a land card, repeat this process.| +Buster Sword|Final Fantasy|351|M|{3}|Artifact - Equipment|||Equipped creature gets +3/+2.$Whenever equipped creature deals combat damage to a player, draw a card, then you may cast a spell from your hand with mana value less than or equal to that damage without paying its mana cost.$Equip {2}| +Summon: Bahamut|Final Fantasy|356|M|{9}|Enchantment Creature - Saga Dragon|9|9|(As this Saga enters and after your draw step, add a lore counter. Sacrifice after IV.)$I, II -- Destroy up to one target nonland permanent.$III -- Draw two cards.$IV -- Mega Flare -- This creature deals damage equal to the total mana value of other permanents you control to each opponent.$Flying| +Summon: Knights of Round|Final Fantasy|359|M|{6}{W}{W}|Enchantment Creature - Saga Knight|3|3|(As this Saga enters and after your draw step, add a lore counter. Sacrifice after V.)$I, II, III, IV -- Create three 2/2 white Knight creature tokens.$V -- Ultimate End -- Other creatures you control get +2/+2 until end of turn. Put an indestructible counter on each of them.$Indestructible| +Summon: Primal Garuda|Final Fantasy|360|U|{3}{W}|Enchantment Creature - Saga Harpy|3|3|(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I -- Aerial Blast -- This creature deals 4 damage to target tapped creature an opponent controls.$II, III -- Slipstream -- Another target creature you control gets +1/+0 and gains flying until end of turn.$Flying| +Summon: Leviathan|Final Fantasy|361|R|{4}{U}{U}|Enchantment Creature - Saga Leviathan|6|6|I -- Return each creature that isn't a Kraken, Leviathan, Merfolk, Octopus, or Serpent to its owner's hand.$II, III -- Until end of turn, whenever a Kraken, Leviathan, Merfolk, Octopus, or Serpent attacks, draw a card.$Ward {2}| +Summon: Shiva|Final Fantasy|362|U|{3}{U}{U}|Enchantment Creature - Saga Elemental|4|5|(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I, II -- Heavenly Strike -- Tap target creature an opponent controls. Put a stun counter on it.$III -- Diamond Dust -- Draw a card for each tapped creature your opponents control.| +Jecht, Reluctant Guardian|Final Fantasy|363|R|{3}{B}|Legendary Creature - Human Warrior|4|3|Menace$Whenever Jecht deals combat damage to a player, you may exile it, then return it to the battlefield transformed under its owner's control.| +Braska's Final Aeon|Final Fantasy|363|R||Legendary Enchantment Creature - Saga Nightmare|7|7|(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I, II -- Jecht Beam -- Each opponent discards a card and you draw a card.$III -- Ultimate Jecht Shot -- Each opponent sacrifices two creatures of their choice.$Menace| +Summon: Anima|Final Fantasy|364|U|{4}{B}{B}|Enchantment Creature - Saga Horror|4|4|(As this Saga enters and after your draw step, add a lore counter. Sacrifice after IV.)$I, II, III -- Pain -- You draw a card and you lose 1 life.$IV -- Oblivion -- Each opponent sacrifices a creature of their choice and loses 3 life.$Menace| +Summon: Primal Odin|Final Fantasy|365|R|{4}{B}{B}|Enchantment Creature - Saga Knight|5|3|(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I -- Gungnir -- Destroy target creature an opponent controls.$II -- Zantetsuken -- This creature gains "Whenever this creature deals combat damage to a player, that player loses the game."$III -- Hall of Sorrow -- Draw two cards. Each player loses 2 life.| +Summon: Esper Ramuh|Final Fantasy|367|U|{2}{R}{R}|Enchantment Creature - Saga Wizard|3|3|(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I -- Judgment Bolt -- This creature deals damage equal to the number of noncreature, nonland cards in your graveyard to target creature an opponent controls.$II, III -- Wizards you control get +1/+0 until end of turn.| +Esper Origins|Final Fantasy|370|R|{1}{G}|Sorcery|||Surveil 2. You gain 2 life. If this spell was cast from a graveyard, exile it, then put it onto the battlefield transformed under its owner's control with a finality counter on it.$Flashback {3}{G}| +Summon: Esper Maduin|Final Fantasy|370|R||Enchantment Creature - Saga Elemental|4|4|(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I -- Reveal the top card of your library. If it's a permanent card, put it into your hand.$II -- Add {G}{G}.$III -- Other creatures you control get +2/+2 and gain trample until end of turn.| +Summon: Fenrir|Final Fantasy|372|U|{2}{G}|Enchantment Creature - Saga Wolf|3|2|(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I -- Crescent Fang -- Search your library for a basic land card, put it onto the battlefield tapped, then shuffle.$II -- Heavenward Howl -- When you next cast a creature spell this turn, that creature enters with an additional +1/+1 counter on it.$III -- Ecliptic Growl -- Draw a card if you control the creature with the greatest power or tied for the greatest power.| +Aerith Gainsborough|Final Fantasy|374|R|{2}{W}|Legendary Creature - Human Cleric|2|2|Lifelink$Whenever you gain life, put a +1/+1 counter on Aerith Gainsborough.$When Aerith Gainsborough dies, put X +1/+1 counters on each legendary creature you control, where X is the number of +1/+1 counters on Aerith Gainsborough.| +Cloud, Midgar Mercenary|Final Fantasy|375|M|{W}{W}|Legendary Creature - Human Soldier Mercenary|2|1|When Cloud enters, search your library for an Equipment card, reveal it, put it into your hand, then shuffle.$As long as Cloud is equipped, if an ability of Cloud or an Equipment attached to it triggers, that ability triggers an additional time.| +Jill, Shiva's Dominant|Final Fantasy|378|R|{2}{U}|Legendary Creature - Human Noble Warrior|2|2|When Jill enters, return up to one other target nonland permanent to its owner's hand.${3}{U}{U}, {T}: Exile Jill, then return it to the battlefield transformed under its owner's control. Activate only as a sorcery.| +Shiva, Warden of Ice|Final Fantasy|378|R||Legendary Enchantment Creature - Saga Elemental|4|5|(As this Saga enters and after your draw step, add a lore counter.)$I, II -- Mesmerize -- Target creature can't be blocked this turn.$III -- Cold Snap -- Tap all lands your opponents control. Exile Shiva, then return it to the battlefield.| +Ardyn, the Usurper|Final Fantasy|379|R|{5}{B}{B}{B}|Legendary Creature - Elder Human Noble|4|4|Demons you control have menace, lifelink, and haste.$Starscourge -- At the beginning of combat on your turn, exile up to one target creature card from a graveyard. If you exiled a card this way, create a token that's a copy of that card, except it's a 5/5 black Demon.| +Cecil, Dark Knight|Final Fantasy|380|R|{B}|Legendary Creature - Human Knight|2|3|Deathtouch$Darkness -- Whenever Cecil deals damage, you lose that much life. Then if your life total is less than or equal to half your starting life total, untap Cecil and transform it.| +Cecil, Redeemed Paladin|Final Fantasy|380|R||Legendary Creature - Human Knight|4|4|Lifelink$Protect -- Whenever Cecil attacks, other attacking creatures gain indestructible until end of turn.| +Fang, Fearless l'Cie|Final Fantasy|381|U|{2}{B}|Legendary Creature - Human Warrior|2|3|Whenever one or more cards leave your graveyard, you draw a card and you lose 1 life. This ability triggers only once each turn.$(Melds with Vanille, Cheerful l'Cie.)| +Ragnarok, Divine Deliverance|Final Fantasy|381b|U||Legendary Creature - Beast Avatar|7|6|Vigilance, menace, trample, reach, haste$When Ragnarok dies, destroy target permanent and return target nonlegendary permanent card from your graveyard to the battlefield.| +Sephiroth, Fabled SOLDIER|Final Fantasy|382|M|{2}{B}|Legendary Creature - Human Avatar Soldier|3|3|Whenever Sephiroth enters or attacks, you may sacrifice another creature. If you do, draw a card.$Whenever another creature dies, target opponent loses 1 life and you gain 1 life. If this is the fourth time this ability has resolved this turn, transform Sephiroth.| +Sephiroth, One-Winged Angel|Final Fantasy|382|M||Legendary Creature - Angel Nightmare Avatar|5|5|Flying$Super Nova -- As this creature transforms into Sephiroth, One-Winged Angel, you get an emblem with "Whenever a creature dies, target opponent loses 1 life and you gain 1 life."$Whenever Sephiroth attacks, you may sacrifice any number of other creatures. If you do, draw that many cards.| +Vincent Valentine|Final Fantasy|383|R|{2}{B}{B}|Legendary Creature - Assassin|2|2|Whenever a creature an opponent controls dies, put a number of +1/+1 counters on Vincent Valentine equal to that creature's power.$Whenever Vincent Valentine attacks, you may transform it.| +Galian Beast|Final Fantasy|383|R||Legendary Creature - Werewolf Beast|3|2|Trample, lifelink$When Galian Beast dies, return it to the battlefield tapped.| +Zenos yae Galvus|Final Fantasy|384|R|{3}{B}{B}|Legendary Creature - Human Noble Warrior|4|4|My First Friend -- When Zenos yae Galvus enters, choose a creature an opponent controls. Until end of turn, creatures other than Zenos yae Galvus and the chosen creature get -2/-2.$When the chosen creature leaves the battlefield, transform Zenos yae Galvus.| +Shinryu, Transcendent Rival|Final Fantasy|384|R||Legendary Creature - Dragon|8|8|Flying$As this creature transforms into Shinryu, choose an opponent.$Burning Chains -- When the chosen player loses the game, you win the game.| +Clive, Ifrit's Dominant|Final Fantasy|385|M|{4}{R}{R}|Legendary Creature - Human Noble Warrior|5|5|When Clive enters, you may discard your hand, then draw cards equal to your devotion to red.${4}{R}{R}, {T}: Exile Clive, then return it to the battlefield transformed under its owner's control. Activate only as a sorcery.| +Ifrit, Warden of Inferno|Final Fantasy|385|M||Legendary Enchantment Creature - Saga Demon|9|9|(As this Saga enters and after your draw step, add a lore counter.)$I -- Lunge -- Ifrit fights up to one other target creature.$II, III -- Brimstone -- Add {R}{R}{R}{R}. If Ifrit has three or more lore counters on it, exile it, then return it to the battlefield| +Firion, Wild Rose Warrior|Final Fantasy|386|R|{2}{R}|Legendary Creature - Human Rebel Warrior|3|3|Equipped creatures you control have haste.$Whenever a nontoken Equipment you control enters, create a token that's a copy of it, except it has "This Equipment's equip abilities cost {2} less to activate." Sacrifice that token at the beginning of the next upkeep.| +Tifa Lockhart|Final Fantasy|391|R|{1}{G}|Legendary Creature - Human Monk|1|2|Trample$Landfall -- Whenever a land you control enters, double Tifa Lockhart's power until end of turn.| +Vanille, Cheerful l'Cie|Final Fantasy|392|U|{3}{G}|Legendary Creature - Human Cleric|3|2|When Vanille enters, mill two cards, then return a permanent card from your graveyard to your hand.$At the beginning of your first main phase, if you both own and control Vanille and a creature named Fang, Fearless l'Cie, you may pay {3}{B}{G}. If you do, exile them, then meld them into Ragnarok, Divine Deliverance.| +Balthier and Fran|Final Fantasy|393|R|{1}{R}{G}|Legendary Creature - Human Rabbit|4|3|Reach$Vehicles you control get +1/+1 and have vigilance and reach.$Whenever a Vehicle crewed by Balthier and Fran this turn attacks, if it's the first combat phase of the turn, you may pay {1}{R}{G}. If you do, after this phase, there is an additional combat phase.| +Emet-Selch, Unsundered|Final Fantasy|394|M|{1}{U}{B}|Legendary Creature - Elder Wizard|2|4|Vigilance$Whenever Emet-Selch enters or attacks, draw a card, then discard a card.$At the beginning of your upkeep, if there are fourteen or more cards in your graveyard, you may transform Emet-Selch.| +Hades, Sorcerer of Eld|Final Fantasy|394|M||Legendary Creature - Avatar|6|6|Vigilance$Echo of the Lost -- During your turn, you may play cards from your graveyard.$If a card or token would be put into your graveyard from anywhere, exile it instead.| +Kefka, Court Mage|Final Fantasy|398|M|{2}{U}{B}{R}|Legendary Creature - Human Wizard|4|5|Whenever Kefka enters or attacks, each player discards a card. Then you draw a card for each card type among cards discarded this way.${8}: Each opponent sacrifices a permanent of their choice. Transform Kefka. Activate only as a sorcery.| +Kefka, Ruler of Ruin|Final Fantasy|398|M||Legendary Creature - Avatar Wizard|5|7|Flying$Whenever an opponent loses life during your turn, you draw that many cards.| +Kuja, Genome Sorcerer|Final Fantasy|399|R|{2}{B}{R}|Legendary Creature - Human Mutant Wizard|3|4|At the beginning of your end step, create a tapped 0/1 black Wizard creature token with "Whenever you cast a noncreature spell, this token deals 1 damage to each opponent." Then if you control four or more Wizards, transform Kuja.| +Trance Kuja, Fate Defied|Final Fantasy|399|R||Legendary Creature - Avatar Wizard|4|6|Flame Star -- If a Wizard you control would deal damage to a permanent or player, it deals double that damage instead.| +Lightning, Army of One|Final Fantasy|400|M|{1}{R}{W}|Legendary Creature - Human Soldier|3|2|First strike, trample, lifelink$Stagger -- Whenever Lightning deals combat damage to a player, until your next turn, if a source would deal damage to that player or a permanent that player controls, it deals double that damage instead.| +Noctis, Prince of Lucis|Final Fantasy|401|R|{1}{W}{U}{B}|Legendary Creature - Human Noble|4|3|Lifelink$You may cast artifact spells from your graveyard by paying 3 life in addition to paying their other costs. If you cast a spell this way, that artifact enters with a finality counter on it.| +Squall, SeeD Mercenary|Final Fantasy|402|R|{2}{W}{B}|Legendary Creature - Human Knight Mercenary|3|4|Rough Divide -- Whenever a creature you control attacks alone, it gains double strike until end of turn.$Whenever Squall deals combat damage to a player, return target permanent card with mana value 3 or less from your graveyard to the battlefield.| +Yuna, Hope of Spira|Final Fantasy|404|M|{3}{G}{W}|Legendary Creature - Human Cleric|3|5|During your turn, Yuna and enchantment creatures you control have trample, lifelink, and ward {2}.$At the beginning of your end step, return up to one target enchantment card from your graveyard to the battlefield with a finality counter on it.| +Zidane, Tantalus Thief|Final Fantasy|405|U|{3}{R}{W}|Legendary Creature - Human Mutant Scout|3|3|When Zidane enters, gain control of target creature an opponent controls until end of turn. Untap that creature. It gains lifelink and haste until end of turn.$Whenever an opponent gains control of a permanent you control, create a Treasure token.| +Traveling Chocobo|Final Fantasy|406|M|{2}{G}|Creature - Bird|3|2|You may look at the top card of your library any time.$You may play lands and cast Bird spells from the top of your library.$If a land or Bird you control entering the battlefield causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time.| +Cid, Timeless Artificer|Final Fantasy|407|U|{2}{W}{U}|Legendary Creature - Human Artificer|4|4|Artifact creatures and Heroes you control get +1/+1 for each Artificer you control and each Artificer card in your graveyard.$A deck can have any number of cards named Cid, Timeless Artificer.$Cycling {W}{U}| +Cid, Timeless Artificer|Final Fantasy|408|U|{2}{W}{U}|Legendary Creature - Human Artificer|4|4|Artifact creatures and Heroes you control get +1/+1 for each Artificer you control and each Artificer card in your graveyard.$A deck can have any number of cards named Cid, Timeless Artificer.$Cycling {W}{U}| +Cid, Timeless Artificer|Final Fantasy|409|U|{2}{W}{U}|Legendary Creature - Human Artificer|4|4|Artifact creatures and Heroes you control get +1/+1 for each Artificer you control and each Artificer card in your graveyard.$A deck can have any number of cards named Cid, Timeless Artificer.$Cycling {W}{U}| +Cid, Timeless Artificer|Final Fantasy|410|U|{2}{W}{U}|Legendary Creature - Human Artificer|4|4|Artifact creatures and Heroes you control get +1/+1 for each Artificer you control and each Artificer card in your graveyard.$A deck can have any number of cards named Cid, Timeless Artificer.$Cycling {W}{U}| +Cid, Timeless Artificer|Final Fantasy|411|U|{2}{W}{U}|Legendary Creature - Human Artificer|4|4|Artifact creatures and Heroes you control get +1/+1 for each Artificer you control and each Artificer card in your graveyard.$A deck can have any number of cards named Cid, Timeless Artificer.$Cycling {W}{U}| +Cid, Timeless Artificer|Final Fantasy|412|U|{2}{W}{U}|Legendary Creature - Human Artificer|4|4|Artifact creatures and Heroes you control get +1/+1 for each Artificer you control and each Artificer card in your graveyard.$A deck can have any number of cards named Cid, Timeless Artificer.$Cycling {W}{U}| +Cid, Timeless Artificer|Final Fantasy|413|U|{2}{W}{U}|Legendary Creature - Human Artificer|4|4|Artifact creatures and Heroes you control get +1/+1 for each Artificer you control and each Artificer card in your graveyard.$A deck can have any number of cards named Cid, Timeless Artificer.$Cycling {W}{U}| +Cid, Timeless Artificer|Final Fantasy|414|U|{2}{W}{U}|Legendary Creature - Human Artificer|4|4|Artifact creatures and Heroes you control get +1/+1 for each Artificer you control and each Artificer card in your graveyard.$A deck can have any number of cards named Cid, Timeless Artificer.$Cycling {W}{U}| +Cid, Timeless Artificer|Final Fantasy|415|U|{2}{W}{U}|Legendary Creature - Human Artificer|4|4|Artifact creatures and Heroes you control get +1/+1 for each Artificer you control and each Artificer card in your graveyard.$A deck can have any number of cards named Cid, Timeless Artificer.$Cycling {W}{U}| +Cid, Timeless Artificer|Final Fantasy|416|U|{2}{W}{U}|Legendary Creature - Human Artificer|4|4|Artifact creatures and Heroes you control get +1/+1 for each Artificer you control and each Artificer card in your graveyard.$A deck can have any number of cards named Cid, Timeless Artificer.$Cycling {W}{U}| +Cid, Timeless Artificer|Final Fantasy|417|U|{2}{W}{U}|Legendary Creature - Human Artificer|4|4|Artifact creatures and Heroes you control get +1/+1 for each Artificer you control and each Artificer card in your graveyard.$A deck can have any number of cards named Cid, Timeless Artificer.$Cycling {W}{U}| +Cid, Timeless Artificer|Final Fantasy|418|U|{2}{W}{U}|Legendary Creature - Human Artificer|4|4|Artifact creatures and Heroes you control get +1/+1 for each Artificer you control and each Artificer card in your graveyard.$A deck can have any number of cards named Cid, Timeless Artificer.$Cycling {W}{U}| +Cid, Timeless Artificer|Final Fantasy|419|U|{2}{W}{U}|Legendary Creature - Human Artificer|4|4|Artifact creatures and Heroes you control get +1/+1 for each Artificer you control and each Artificer card in your graveyard.$A deck can have any number of cards named Cid, Timeless Artificer.$Cycling {W}{U}| +Cid, Timeless Artificer|Final Fantasy|420|U|{2}{W}{U}|Legendary Creature - Human Artificer|4|4|Artifact creatures and Heroes you control get +1/+1 for each Artificer you control and each Artificer card in your graveyard.$A deck can have any number of cards named Cid, Timeless Artificer.$Cycling {W}{U}| +Ultima, Origin of Oblivion|Final Fantasy|421|R|{5}|Legendary Creature - God|4|4|Flying$Whenever Ultima attacks, put a blight counter on target land. For as long as that land has a blight counter on it, it loses all land types and abilities and has "{T}: Add {C}."$Whenever you tap a land for {C}, add an additional {C}.| +Aerith Gainsborough|Final Fantasy|423|R|{2}{W}|Legendary Creature - Human Cleric|2|2|Lifelink$Whenever you gain life, put a +1/+1 counter on Aerith Gainsborough.$When Aerith Gainsborough dies, put X +1/+1 counters on each legendary creature you control, where X is the number of +1/+1 counters on Aerith Gainsborough.| +Beatrix, Loyal General|Final Fantasy|426|R|{4}{W}{W}|Legendary Creature - Human Soldier|4|4|Vigilance$At the beginning of combat on your turn, you may attach any number of Equipment you control to target creature you control.| +Cloud, Midgar Mercenary|Final Fantasy|427|M|{W}{W}|Legendary Creature - Human Soldier Mercenary|2|1|When Cloud enters, search your library for an Equipment card, reveal it, put it into your hand, then shuffle.$As long as Cloud is equipped, if an ability of Cloud or an Equipment attached to it triggers, that ability triggers an additional time.| +Rosa, Resolute White Mage|Final Fantasy|431|R|{3}{W}|Legendary Creature - Human Noble Cleric|2|3|Reach$At the beginning of combat on your turn, put a +1/+1 counter on target creature you control. It gains lifelink until end of turn.| +Stiltzkin, Moogle Merchant|Final Fantasy|433|R|{W}|Legendary Creature - Moogle|1|2|Lifelink${2}, {T}: Target opponent gains control of another target permanent you control. If they do, you draw a card.| +Jill, Shiva's Dominant|Final Fantasy|438|R|{2}{U}|Legendary Creature - Human Noble Warrior|2|2|When Jill enters, return up to one other target nonland permanent to its owner's hand.${3}{U}{U}, {T}: Exile Jill, then return it to the battlefield transformed under its owner's control. Activate only as a sorcery.| +Shiva, Warden of Ice|Final Fantasy|438|R||Legendary Enchantment Creature - Saga Elemental|4|5|(As this Saga enters and after your draw step, add a lore counter.)$I, II -- Mesmerize -- Target creature can't be blocked this turn.$III -- Cold Snap -- Tap all lands your opponents control. Exile Shiva, then return it to the battlefield.| +Matoya, Archon Elder|Final Fantasy|439|R|{2}{U}|Legendary Creature - Human Warlock|1|4|Whenever you scry or surveil, draw a card.| +Quistis Trepe|Final Fantasy|440|U|{2}{U}|Legendary Creature - Human Wizard|2|2|Blue Magic -- When Quistis Trepe enters, you may cast target instant or sorcery card from a graveyard, and mana of any type can be spent to cast that spell. If that spell would be put into a graveyard, exile it instead.| +Ultimecia, Temporal Threat|Final Fantasy|441|R|{4}{U}{U}|Legendary Creature - Human Warlock|4|4|When Ultimecia enters, tap all creatures your opponents control.$Whenever a creature you control deals combat damage to a player, draw a card.| +Y'shtola Rhul|Final Fantasy|443|M|{4}{U}{U}|Legendary Creature - Cat Druid|3|5|At the beginning of your end step, exile target creature you control, then return it to the battlefield under its owner's control. Then if it's the first end step of the turn, there is an additional end step after this step.| +Ardyn, the Usurper|Final Fantasy|444|R|{5}{B}{B}{B}|Legendary Creature - Elder Human Noble|4|4|Demons you control have menace, lifelink, and haste.$Starscourge -- At the beginning of combat on your turn, exile up to one target creature card from a graveyard. If you exiled a card this way, create a token that's a copy of that card, except it's a 5/5 black Demon.| +Cecil, Dark Knight|Final Fantasy|445|R|{B}|Legendary Creature - Human Knight|2|3|Deathtouch$Darkness -- Whenever Cecil deals damage, you lose that much life. Then if your life total is less than or equal to half your starting life total, untap Cecil and transform it.| +Cecil, Redeemed Paladin|Final Fantasy|445|R||Legendary Creature - Human Knight|4|4|Lifelink$Protect -- Whenever Cecil attacks, other attacking creatures gain indestructible until end of turn.| +Fang, Fearless l'Cie|Final Fantasy|446|U|{2}{B}|Legendary Creature - Human Warrior|2|3|Whenever one or more cards leave your graveyard, you draw a card and you lose 1 life. This ability triggers only once each turn.$(Melds with Vanille, Cheerful l'Cie.)| +Ragnarok, Divine Deliverance|Final Fantasy|446b|U||Legendary Creature - Beast Avatar|7|6|Vigilance, menace, trample, reach, haste$When Ragnarok dies, destroy target permanent and return target nonlegendary permanent card from your graveyard to the battlefield.| +Jecht, Reluctant Guardian|Final Fantasy|448|R|{3}{B}|Legendary Creature - Human Warrior|4|3|Menace$Whenever Jecht deals combat damage to a player, you may exile it, then return it to the battlefield transformed under its owner's control.| +Braska's Final Aeon|Final Fantasy|448|R||Legendary Enchantment Creature - Saga Nightmare|7|7|(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I, II -- Jecht Beam -- Each opponent discards a card and you draw a card.$III -- Ultimate Jecht Shot -- Each opponent sacrifices two creatures of their choice.$Menace| +Kain, Traitorous Dragoon|Final Fantasy|449|R|{2}{B}|Legendary Creature - Human Knight|2|4|Jump -- During your turn, Kain has flying.$Whenever Kain deals combat damage to a player, that player gains control of Kain. If they do, you draw that many cards, create that many tapped Treasure tokens, then lose that much life.| +Sephiroth, Fabled SOLDIER|Final Fantasy|451|M|{2}{B}|Legendary Creature - Human Avatar Soldier|3|3|Whenever Sephiroth enters or attacks, you may sacrifice another creature. If you do, draw a card.$Whenever another creature dies, target opponent loses 1 life and you gain 1 life. If this is the fourth time this ability has resolved this turn, transform Sephiroth.| +Sephiroth, One-Winged Angel|Final Fantasy|451|M||Legendary Creature - Angel Nightmare Avatar|5|5|Flying$Super Nova -- As this creature transforms into Sephiroth, One-Winged Angel, you get an emblem with "Whenever a creature dies, target opponent loses 1 life and you gain 1 life."$Whenever Sephiroth attacks, you may sacrifice any number of other creatures. If you do, draw that many cards.| +Seymour Flux|Final Fantasy|452|R|{4}{B}|Legendary Creature - Spirit Avatar|5|5|At the beginning of your upkeep, you may pay 1 life. If you do, draw a card and put a +1/+1 counter on Seymour Flux.| +Vincent Valentine|Final Fantasy|454|R|{2}{B}{B}|Legendary Creature - Assassin|2|2|Whenever a creature an opponent controls dies, put a number of +1/+1 counters on Vincent Valentine equal to that creature's power.$Whenever Vincent Valentine attacks, you may transform it.| +Galian Beast|Final Fantasy|454|R||Legendary Creature - Werewolf Beast|3|2|Trample, lifelink$When Galian Beast dies, return it to the battlefield tapped.| +Zenos yae Galvus|Final Fantasy|455|R|{3}{B}{B}|Legendary Creature - Human Noble Warrior|4|4|My First Friend -- When Zenos yae Galvus enters, choose a creature an opponent controls. Until end of turn, creatures other than Zenos yae Galvus and the chosen creature get -2/-2.$When the chosen creature leaves the battlefield, transform Zenos yae Galvus.| +Shinryu, Transcendent Rival|Final Fantasy|455|R||Legendary Creature - Dragon|8|8|Flying$As this creature transforms into Shinryu, choose an opponent.$Burning Chains -- When the chosen player loses the game, you win the game.| +Zodiark, Umbral God|Final Fantasy|456|R|{B}{B}{B}{B}{B}|Legendary Creature - God|5|5|Indestructible$When Zodiark enters, each player sacrifices half the non-God creatures they control of their choice, rounded down.$Whenever a player sacrifices another creature, put a +1/+1 counter on Zodiark.| +Clive, Ifrit's Dominant|Final Fantasy|458|M|{4}{R}{R}|Legendary Creature - Human Noble Warrior|5|5|When Clive enters, you may discard your hand, then draw cards equal to your devotion to red.${4}{R}{R}, {T}: Exile Clive, then return it to the battlefield transformed under its owner's control. Activate only as a sorcery.| +Ifrit, Warden of Inferno|Final Fantasy|458|M||Legendary Enchantment Creature - Saga Demon|9|9|(As this Saga enters and after your draw step, add a lore counter.)$I -- Lunge -- Ifrit fights up to one other target creature.$II, III -- Brimstone -- Add {R}{R}{R}{R}. If Ifrit has three or more lore counters on it, exile it, then return it to the battlefield| +Firion, Wild Rose Warrior|Final Fantasy|459|R|{2}{R}|Legendary Creature - Human Rebel Warrior|3|3|Equipped creatures you control have haste.$Whenever a nontoken Equipment you control enters, create a token that's a copy of it, except it has "This Equipment's equip abilities cost {2} less to activate." Sacrifice that token at the beginning of the next upkeep.| +Freya Crescent|Final Fantasy|460|U|{R}|Legendary Creature - Rat Knight|1|1|Jump -- During your turn, Freya Crescent has flying.${T}: Add {R}. Spend this mana only to cast an Equipment spell or activate an equip ability.| +Gilgamesh, Master-at-Arms|Final Fantasy|461|R|{4}{R}{R}|Legendary Creature - Human Samurai|6|6|Whenever Gilgamesh enters or attacks, look at the top six cards of your library. You may put any number of Equipment cards from among them onto the battlefield. Put the rest on the bottom of your library in a random order. When you put one or more Equipment onto the battlefield this way, you may attach one of them to a Samurai you control.| +Lightning, Security Sergeant|Final Fantasy|462|R|{2}{R}|Legendary Creature - Human Soldier|2|3|Menace$Whenever Lightning deals combat damage to a player, exile the top card of your library. You may play that card for as long as you control Lightning.| +Zell Dincht|Final Fantasy|468|R|{2}{R}|Legendary Creature - Human Monk|0|3|You may play an additional land on each of your turns.$Zell Dincht gets +1/+0 for each land you control.$At the beginning of your end step, return a land you control to its owner's hand.| +Bartz and Boko|Final Fantasy|469|R|{3}{G}{G}|Legendary Creature - Human Bird|4|3|Affinity for Birds$When Bartz and Boko enters, each other Bird you control deals damage equal to its power to target creature an opponent controls.| +Quina, Qu Gourmet|Final Fantasy|471|U|{2}{G}|Legendary Creature - Qu|2|3|If one or more tokens would be created under your control, those tokens plus a 1/1 green Frog creature token are created instead.${2}, Sacrifice a Frog: Put a +1/+1 counter on Quina.| +Sazh Katzroy|Final Fantasy|472|R|{3}{G}|Legendary Creature - Human Pilot|3|3|When Sazh Katzroy enters, you may search your library for a Bird or basic land card, reveal it, put it into your hand, then shuffle.$Whenever Sazh Katzroy attacks, put a +1/+1 counter on target creature, then double the number of +1/+1 counters on that creature.| +Tifa Lockhart|Final Fantasy|473|R|{1}{G}|Legendary Creature - Human Monk|1|2|Trample$Landfall -- Whenever a land you control enters, double Tifa Lockhart's power until end of turn.| +Vanille, Cheerful l'Cie|Final Fantasy|475|U|{3}{G}|Legendary Creature - Human Cleric|3|2|When Vanille enters, mill two cards, then return a permanent card from your graveyard to your hand.$At the beginning of your first main phase, if you both own and control Vanille and a creature named Fang, Fearless l'Cie, you may pay {3}{B}{G}. If you do, exile them, then meld them into Ragnarok, Divine Deliverance.| +Absolute Virtue|Final Fantasy|476|M|{6}{W}{U}|Legendary Creature - Avatar Warrior|8|8|This spell can't be countered.$Flying$You have protection from each of your opponents.| +Balthier and Fran|Final Fantasy|477|R|{1}{R}{G}|Legendary Creature - Human Rabbit|4|3|Reach$Vehicles you control get +1/+1 and have vigilance and reach.$Whenever a Vehicle crewed by Balthier and Fran this turn attacks, if it's the first combat phase of the turn, you may pay {1}{R}{G}. If you do, after this phase, there is an additional combat phase.| +Choco, Seeker of Paradise|Final Fantasy|479|R|{1}{G}{W}{U}|Legendary Creature - Bird|3|5|Whenever one or more Birds you control attack, look at that many cards from the top of your library. You may put one of them into your hand. Then put any number of land cards from among them onto the battlefield tapped and the rest into your graveyard.$Landfall -- Whenever a land you control enters, Choco gets +1/+0 until end of turn.| +Cid, Timeless Artificer|Final Fantasy|480|U|{2}{W}{U}|Legendary Creature - Human Artificer|4|4|Artifact creatures and Heroes you control get +1/+1 for each Artificer you control and each Artificer card in your graveyard.$A deck can have any number of cards named Cid, Timeless Artificer.$Cycling {W}{U}| +Cloud, Planet's Champion|Final Fantasy|482|M|{3}{R}{W}|Legendary Creature - Human Soldier Mercenary|4|4|During your turn, as long as Cloud is equipped, it has double strike and indestructible.$Equip abilities you activate that target Cloud cost {2} less to activate.| +Emet-Selch, Unsundered|Final Fantasy|483|M|{1}{U}{B}|Legendary Creature - Elder Wizard|2|4|Vigilance$Whenever Emet-Selch enters or attacks, draw a card, then discard a card.$At the beginning of your upkeep, if there are fourteen or more cards in your graveyard, you may transform Emet-Selch.| +Hades, Sorcerer of Eld|Final Fantasy|483|M||Legendary Creature - Avatar|6|6|Vigilance$Echo of the Lost -- During your turn, you may play cards from your graveyard.$If a card or token would be put into your graveyard from anywhere, exile it instead.| +The Emperor of Palamecia|Final Fantasy|484|U|{U}{R}|Legendary Creature - Human Noble Wizard|2|2|{T}: Add {U} or {R}. Spend this mana only to cast a noncreature spell.$Whenever you cast a noncreature spell, if at least four mana was spent to cast it, put a +1/+1 counter on The Emperor of Palamecia. Then if it has three or more +1/+1 counters on it, transform it.| +The Lord Master of Hell|Final Fantasy|484|U||Legendary Creature - Demon Noble Wizard|3|3|Starfall -- Whenever The Lord Master of Hell attacks, it deals X damage to each opponent, where X is the number of noncreature, nonland cards in your graveyard.| +Garland, Knight of Cornelia|Final Fantasy|486|U|{B}{R}|Legendary Creature - Human Knight|3|2|Whenever you cast a noncreature spell, surveil 1.${3}{B}{B}{R}{R}: Return this card from your graveyard to the battlefield transformed. Activate only as a sorcery.| +Chaos, the Endless|Final Fantasy|486|U||Legendary Creature - Demon|5|5|Flying$When Chaos dies, put it on the bottom of its owner's library.| +Gladiolus Amicitia|Final Fantasy|489|U|{4}{R}{G}|Legendary Creature - Human Warrior|6|6|When Gladiolus Amicitia enters, search your library for a land card, put it onto the battlefield tapped, then shuffle.$Landfall -- Whenever a land you control enters, another target creature you control gets +2/+2 and gains trample until end of turn.| +Kefka, Court Mage|Final Fantasy|496|M|{2}{U}{B}{R}|Legendary Creature - Human Wizard|4|5|Whenever Kefka enters or attacks, each player discards a card. Then you draw a card for each card type among cards discarded this way.${8}: Each opponent sacrifices a permanent of their choice. Transform Kefka. Activate only as a sorcery.| +Kefka, Ruler of Ruin|Final Fantasy|496|M||Legendary Creature - Avatar Wizard|5|7|Flying$Whenever an opponent loses life during your turn, you draw that many cards.| +Kuja, Genome Sorcerer|Final Fantasy|497|R|{2}{B}{R}|Legendary Creature - Human Mutant Wizard|3|4|At the beginning of your end step, create a tapped 0/1 black Wizard creature token with "Whenever you cast a noncreature spell, this token deals 1 damage to each opponent." Then if you control four or more Wizards, transform Kuja.| +Trance Kuja, Fate Defied|Final Fantasy|497|R||Legendary Creature - Avatar Wizard|4|6|Flame Star -- If a Wizard you control would deal damage to a permanent or player, it deals double that damage instead.| +Lightning, Army of One|Final Fantasy|498|M|{1}{R}{W}|Legendary Creature - Human Soldier|3|2|First strike, trample, lifelink$Stagger -- Whenever Lightning deals combat damage to a player, until your next turn, if a source would deal damage to that player or a permanent that player controls, it deals double that damage instead.| +Noctis, Prince of Lucis|Final Fantasy|500|R|{1}{W}{U}{B}|Legendary Creature - Human Noble|4|3|Lifelink$You may cast artifact spells from your graveyard by paying 3 life in addition to paying their other costs. If you cast a spell this way, that artifact enters with a finality counter on it.| +Rinoa Heartilly|Final Fantasy|502|U|{3}{G}{W}|Legendary Creature - Human Rebel Warlock|4|4|When Rinoa Heartilly enters, create Angelo, a legendary 1/1 green and white Dog creature token.$Angelo Cannon -- Whenever Rinoa Heartilly attacks, another target creature you control gets +1/+1 until end of turn for each creature you control.| +Sephiroth, Planet's Heir|Final Fantasy|505|M|{4}{U}{B}|Legendary Creature - Human Avatar Soldier|4|4|Vigilance$When Sephiroth enters, creatures your opponents control get -2/-2 until end of turn.$Whenever a creature an opponent controls dies, put a +1/+1 counter on Sephiroth.| +Serah Farron|Final Fantasy|506|R|{1}{G}{W}|Legendary Creature - Human Citizen|2|2|The first legendary creature spell you cast each turn costs {2} less to cast.$At the beginning of combat on your turn, if you control two or more other legendary creatures, you may transform Serah Farron.| +Crystallized Serah|Final Fantasy|506|R||Legendary Artifact|||The first legendary creature spell you cast each turn costs {2} less to cast.$Legendary creatures you control get +2/+2.| +Shantotto, Tactician Magician|Final Fantasy|507|U|{1}{U}{R}|Legendary Creature - Dwarf Wizard|0|4|Whenever you cast a noncreature spell, Shantotto gets +X/+0 until end of turn, where X is the amount of mana spent to cast that spell. If X is 4 or more, draw a card.| +Sin, Spira's Punishment|Final Fantasy|508|R|{4}{B}{G}{U}|Legendary Creature - Leviathan Avatar|7|7|Flying$Whenever Sin enters or attacks, exile a permanent card from your graveyard at random, then create a tapped token that's a copy of that card. If the exiled card is a land card, repeat this process.| +Squall, SeeD Mercenary|Final Fantasy|509|R|{2}{W}{B}|Legendary Creature - Human Knight Mercenary|3|4|Rough Divide -- Whenever a creature you control attacks alone, it gains double strike until end of turn.$Whenever Squall deals combat damage to a player, return target permanent card with mana value 3 or less from your graveyard to the battlefield.| +Terra, Magical Adept|Final Fantasy|511|M|{1}{R}{G}|Legendary Creature - Human Wizard Warrior|4|2|When Terra enters, mill five cards. Put up to one enchantment card milled this way into your hand.$Trance -- {4}{R}{G}, {T}: Exile Terra, then return it to the battlefield transformed under its owner's control. Activate only as a sorcery.| +Esper Terra|Final Fantasy|511|M||Legendary Enchantment Creature - Saga Wizard|6|6|(As this Saga enters and after your draw step, add a lore counter.)$I, II, III -- Create a token that's a copy of target nonlegendary enchantment you control. It gains haste. If it's a Saga, put up to three lore counters on it. Sacrifice it at the beginning of your next end step.$IV -- Add {W}{W}, {U}{U}, {B}{B}, {R}{R}, and {G}{G}. Exile Esper Terra, then return it to the battlefield.$Flying| +Ultimecia, Time Sorceress|Final Fantasy|513|U|{3}{U}{B}|Legendary Creature - Human Warlock|4|5|Whenever Ultimecia enters or attacks, surveil 2.$At the beginning of your end step, you may pay {4}{U}{U}{B}{B} and exile eight cards from your graveyard. If you do, transform Ultimecia.| +Ultimecia, Omnipotent|Final Fantasy|513|U||Legendary Creature - Nightmare Warlock|7|7|Menace$Time Compression -- When this creature transforms into Ultimecia, Omnipotent, take an extra turn after this one.| +Vivi Ornitier|Final Fantasy|514|M|{1}{U}{R}|Legendary Creature - Wizard|0|3|{0}: Add X mana in any combination of {U} and/or {R}, where X is Vivi Ornitier's power. Activate only during your turn and only once each turn.$Whenever you cast a noncreature spell, put a +1/+1 counter on Vivi Ornitier and it deals 1 damage to each opponent.| +Xande, Dark Mage|Final Fantasy|516|R|{2}{U}{B}|Legendary Creature - Human Wizard|3|3|Menace$Xande gets +1/+1 for each noncreature, nonland card in your graveyard.| +Yuna, Hope of Spira|Final Fantasy|517|M|{3}{G}{W}|Legendary Creature - Human Cleric|3|5|During your turn, Yuna and enchantment creatures you control have trample, lifelink, and ward {2}.$At the beginning of your end step, return up to one target enchantment card from your graveyard to the battlefield with a finality counter on it.| +Zidane, Tantalus Thief|Final Fantasy|518|U|{3}{R}{W}|Legendary Creature - Human Mutant Scout|3|3|When Zidane enters, gain control of target creature an opponent controls until end of turn. Untap that creature. It gains lifelink and haste until end of turn.$Whenever an opponent gains control of a permanent you control, create a Treasure token.| +Aerith Gainsborough|Final Fantasy|519|R|{2}{W}|Legendary Creature - Human Cleric|2|2|Lifelink$Whenever you gain life, put a +1/+1 counter on Aerith Gainsborough.$When Aerith Gainsborough dies, put X +1/+1 counters on each legendary creature you control, where X is the number of +1/+1 counters on Aerith Gainsborough.| +Cloud, Midgar Mercenary|Final Fantasy|520|M|{W}{W}|Legendary Creature - Human Soldier Mercenary|2|1|When Cloud enters, search your library for an Equipment card, reveal it, put it into your hand, then shuffle.$As long as Cloud is equipped, if an ability of Cloud or an Equipment attached to it triggers, that ability triggers an additional time.| +Jill, Shiva's Dominant|Final Fantasy|523|R|{2}{U}|Legendary Creature - Human Noble Warrior|2|2|When Jill enters, return up to one other target nonland permanent to its owner's hand.${3}{U}{U}, {T}: Exile Jill, then return it to the battlefield transformed under its owner's control. Activate only as a sorcery.| +Shiva, Warden of Ice|Final Fantasy|523|R||Legendary Enchantment Creature - Saga Elemental|4|5|(As this Saga enters and after your draw step, add a lore counter.)$I, II -- Mesmerize -- Target creature can't be blocked this turn.$III -- Cold Snap -- Tap all lands your opponents control. Exile Shiva, then return it to the battlefield.| +Ardyn, the Usurper|Final Fantasy|524|R|{5}{B}{B}{B}|Legendary Creature - Elder Human Noble|4|4|Demons you control have menace, lifelink, and haste.$Starscourge -- At the beginning of combat on your turn, exile up to one target creature card from a graveyard. If you exiled a card this way, create a token that's a copy of that card, except it's a 5/5 black Demon.| +Cecil, Dark Knight|Final Fantasy|525|R|{B}|Legendary Creature - Human Knight|2|3|Deathtouch$Darkness -- Whenever Cecil deals damage, you lose that much life. Then if your life total is less than or equal to half your starting life total, untap Cecil and transform it.| +Cecil, Redeemed Paladin|Final Fantasy|525|R||Legendary Creature - Human Knight|4|4|Lifelink$Protect -- Whenever Cecil attacks, other attacking creatures gain indestructible until end of turn.| +Fang, Fearless l'Cie|Final Fantasy|526|U|{2}{B}|Legendary Creature - Human Warrior|2|3|Whenever one or more cards leave your graveyard, you draw a card and you lose 1 life. This ability triggers only once each turn.$(Melds with Vanille, Cheerful l'Cie.)| +Ragnarok, Divine Deliverance|Final Fantasy|526b|U||Legendary Creature - Beast Avatar|7|6|Vigilance, menace, trample, reach, haste$When Ragnarok dies, destroy target permanent and return target nonlegendary permanent card from your graveyard to the battlefield.| +Sephiroth, Fabled SOLDIER|Final Fantasy|527|M|{2}{B}|Legendary Creature - Human Avatar Soldier|3|3|Whenever Sephiroth enters or attacks, you may sacrifice another creature. If you do, draw a card.$Whenever another creature dies, target opponent loses 1 life and you gain 1 life. If this is the fourth time this ability has resolved this turn, transform Sephiroth.| +Sephiroth, One-Winged Angel|Final Fantasy|527|M||Legendary Creature - Angel Nightmare Avatar|5|5|Flying$Super Nova -- As this creature transforms into Sephiroth, One-Winged Angel, you get an emblem with "Whenever a creature dies, target opponent loses 1 life and you gain 1 life."$Whenever Sephiroth attacks, you may sacrifice any number of other creatures. If you do, draw that many cards.| +Vincent Valentine|Final Fantasy|528|R|{2}{B}{B}|Legendary Creature - Assassin|2|2|Whenever a creature an opponent controls dies, put a number of +1/+1 counters on Vincent Valentine equal to that creature's power.$Whenever Vincent Valentine attacks, you may transform it.| +Galian Beast|Final Fantasy|528|R||Legendary Creature - Werewolf Beast|3|2|Trample, lifelink$When Galian Beast dies, return it to the battlefield tapped.| +Zenos yae Galvus|Final Fantasy|529|R|{3}{B}{B}|Legendary Creature - Human Noble Warrior|4|4|My First Friend -- When Zenos yae Galvus enters, choose a creature an opponent controls. Until end of turn, creatures other than Zenos yae Galvus and the chosen creature get -2/-2.$When the chosen creature leaves the battlefield, transform Zenos yae Galvus.| +Shinryu, Transcendent Rival|Final Fantasy|529|R||Legendary Creature - Dragon|8|8|Flying$As this creature transforms into Shinryu, choose an opponent.$Burning Chains -- When the chosen player loses the game, you win the game.| +Clive, Ifrit's Dominant|Final Fantasy|530|M|{4}{R}{R}|Legendary Creature - Human Noble Warrior|5|5|When Clive enters, you may discard your hand, then draw cards equal to your devotion to red.${4}{R}{R}, {T}: Exile Clive, then return it to the battlefield transformed under its owner's control. Activate only as a sorcery.| +Ifrit, Warden of Inferno|Final Fantasy|530|M||Legendary Enchantment Creature - Saga Demon|9|9|(As this Saga enters and after your draw step, add a lore counter.)$I -- Lunge -- Ifrit fights up to one other target creature.$II, III -- Brimstone -- Add {R}{R}{R}{R}. If Ifrit has three or more lore counters on it, exile it, then return it to the battlefield| +Firion, Wild Rose Warrior|Final Fantasy|531|R|{2}{R}|Legendary Creature - Human Rebel Warrior|3|3|Equipped creatures you control have haste.$Whenever a nontoken Equipment you control enters, create a token that's a copy of it, except it has "This Equipment's equip abilities cost {2} less to activate." Sacrifice that token at the beginning of the next upkeep.| +Tifa Lockhart|Final Fantasy|536|R|{1}{G}|Legendary Creature - Human Monk|1|2|Trample$Landfall -- Whenever a land you control enters, double Tifa Lockhart's power until end of turn.| +Vanille, Cheerful l'Cie|Final Fantasy|537|U|{3}{G}|Legendary Creature - Human Cleric|3|2|When Vanille enters, mill two cards, then return a permanent card from your graveyard to your hand.$At the beginning of your first main phase, if you both own and control Vanille and a creature named Fang, Fearless l'Cie, you may pay {3}{B}{G}. If you do, exile them, then meld them into Ragnarok, Divine Deliverance.| +Balthier and Fran|Final Fantasy|538|R|{1}{R}{G}|Legendary Creature - Human Rabbit|4|3|Reach$Vehicles you control get +1/+1 and have vigilance and reach.$Whenever a Vehicle crewed by Balthier and Fran this turn attacks, if it's the first combat phase of the turn, you may pay {1}{R}{G}. If you do, after this phase, there is an additional combat phase.| +Emet-Selch, Unsundered|Final Fantasy|539|M|{1}{U}{B}|Legendary Creature - Elder Wizard|2|4|Vigilance$Whenever Emet-Selch enters or attacks, draw a card, then discard a card.$At the beginning of your upkeep, if there are fourteen or more cards in your graveyard, you may transform Emet-Selch.| +Hades, Sorcerer of Eld|Final Fantasy|539|M||Legendary Creature - Avatar|6|6|Vigilance$Echo of the Lost -- During your turn, you may play cards from your graveyard.$If a card or token would be put into your graveyard from anywhere, exile it instead.| +Kefka, Court Mage|Final Fantasy|543|M|{2}{U}{B}{R}|Legendary Creature - Human Wizard|4|5|Whenever Kefka enters or attacks, each player discards a card. Then you draw a card for each card type among cards discarded this way.${8}: Each opponent sacrifices a permanent of their choice. Transform Kefka. Activate only as a sorcery.| +Kefka, Ruler of Ruin|Final Fantasy|543|M||Legendary Creature - Avatar Wizard|5|7|Flying$Whenever an opponent loses life during your turn, you draw that many cards.| +Kuja, Genome Sorcerer|Final Fantasy|544|R|{2}{B}{R}|Legendary Creature - Human Mutant Wizard|3|4|At the beginning of your end step, create a tapped 0/1 black Wizard creature token with "Whenever you cast a noncreature spell, this token deals 1 damage to each opponent." Then if you control four or more Wizards, transform Kuja.| +Trance Kuja, Fate Defied|Final Fantasy|544|R||Legendary Creature - Avatar Wizard|4|6|Flame Star -- If a Wizard you control would deal damage to a permanent or player, it deals double that damage instead.| +Lightning, Army of One|Final Fantasy|545|M|{1}{R}{W}|Legendary Creature - Human Soldier|3|2|First strike, trample, lifelink$Stagger -- Whenever Lightning deals combat damage to a player, until your next turn, if a source would deal damage to that player or a permanent that player controls, it deals double that damage instead.| +Noctis, Prince of Lucis|Final Fantasy|546|R|{1}{W}{U}{B}|Legendary Creature - Human Noble|4|3|Lifelink$You may cast artifact spells from your graveyard by paying 3 life in addition to paying their other costs. If you cast a spell this way, that artifact enters with a finality counter on it.| +Squall, SeeD Mercenary|Final Fantasy|547|R|{2}{W}{B}|Legendary Creature - Human Knight Mercenary|3|4|Rough Divide -- Whenever a creature you control attacks alone, it gains double strike until end of turn.$Whenever Squall deals combat damage to a player, return target permanent card with mana value 3 or less from your graveyard to the battlefield.| +Yuna, Hope of Spira|Final Fantasy|549|M|{3}{G}{W}|Legendary Creature - Human Cleric|3|5|During your turn, Yuna and enchantment creatures you control have trample, lifelink, and ward {2}.$At the beginning of your end step, return up to one target enchantment card from your graveyard to the battlefield with a finality counter on it.| +Zidane, Tantalus Thief|Final Fantasy|550|U|{3}{R}{W}|Legendary Creature - Human Mutant Scout|3|3|When Zidane enters, gain control of target creature an opponent controls until end of turn. Untap that creature. It gains lifelink and haste until end of turn.$Whenever an opponent gains control of a permanent you control, create a Treasure token.| +Traveling Chocobo|Final Fantasy|551|M|{2}{G}|Creature - Bird|3|2|You may look at the top card of your library any time.$You may play lands and cast Bird spells from the top of your library.$If a land or Bird you control entering the battlefield causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time.| +Traveling Chocobo|Final Fantasy|551a|M|{2}{G}|Creature - Bird|3|2|You may look at the top card of your library any time.$You may play lands and cast Bird spells from the top of your library.$If a land or Bird you control entering the battlefield causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time.| +Traveling Chocobo|Final Fantasy|551b|M|{2}{G}|Creature - Bird|3|2|You may look at the top card of your library any time.$You may play lands and cast Bird spells from the top of your library.$If a land or Bird you control entering the battlefield causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time.| +Traveling Chocobo|Final Fantasy|551c|M|{2}{G}|Creature - Bird|3|2|You may look at the top card of your library any time.$You may play lands and cast Bird spells from the top of your library.$If a land or Bird you control entering the battlefield causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time.| +Traveling Chocobo|Final Fantasy|551d|M|{2}{G}|Creature - Bird|3|2|You may look at the top card of your library any time.$You may play lands and cast Bird spells from the top of your library.$If a land or Bird you control entering the battlefield causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time.| +Traveling Chocobo|Final Fantasy|551f|M|{2}{G}|Creature - Bird|3|2|You may look at the top card of your library any time.$You may play lands and cast Bird spells from the top of your library.$If a land or Bird you control entering the battlefield causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time.| Cloud, Planet's Champion|Final Fantasy|552|M|{3}{R}{W}|Legendary Creature - Human Soldier Mercenary|4|4|During your turn, as long as Cloud is equipped, it has double strike and indestructible.$Equip abilities you activate that target Cloud cost {2} less to activate.| Sephiroth, Planet's Heir|Final Fantasy|553|M|{4}{U}{B}|Legendary Creature - Human Avatar Soldier|4|4|Vigilance$When Sephiroth enters, creatures your opponents control get -2/-2 until end of turn.$Whenever a creature an opponent controls dies, put a +1/+1 counter on Sephiroth.| +Beatrix, Loyal General|Final Fantasy|554|R|{4}{W}{W}|Legendary Creature - Human Soldier|4|4|Vigilance$At the beginning of combat on your turn, you may attach any number of Equipment you control to target creature you control.| +Rosa, Resolute White Mage|Final Fantasy|555|R|{3}{W}|Legendary Creature - Human Noble Cleric|2|3|Reach$At the beginning of combat on your turn, put a +1/+1 counter on target creature you control. It gains lifelink until end of turn.| +Ultimecia, Temporal Threat|Final Fantasy|556|R|{4}{U}{U}|Legendary Creature - Human Warlock|4|4|When Ultimecia enters, tap all creatures your opponents control.$Whenever a creature you control deals combat damage to a player, draw a card.| +Deadly Embrace|Final Fantasy|557|R|{3}{B}{B}|Sorcery|||Destroy target creature an opponent controls. Then draw a card for each creature that died this turn.| +Seymour Flux|Final Fantasy|558|R|{4}{B}|Legendary Creature - Spirit Avatar|5|5|At the beginning of your upkeep, you may pay 1 life. If you do, draw a card and put a +1/+1 counter on Seymour Flux.| +Judgment Bolt|Final Fantasy|559|R|{3}{R}|Instant|||Judgment Bolt deals 5 damage to target creature and X damage to that creature's controller, where X is the number of Equipment you control.| +Lightning, Security Sergeant|Final Fantasy|560|R|{2}{R}|Legendary Creature - Human Soldier|2|3|Menace$Whenever Lightning deals combat damage to a player, exile the top card of your library. You may play that card for as long as you control Lightning.| +Xande, Dark Mage|Final Fantasy|561|R|{2}{U}{B}|Legendary Creature - Human Wizard|3|3|Menace$Xande gets +1/+1 for each noncreature, nonland card in your graveyard.| +Magitek Scythe|Final Fantasy|562|R|{4}|Artifact - Equipment|||A Test of Your Reflexes! -- When this Equipment enters, you may attach it to target creature you control. If you do, that creature gains first strike until end of turn and must be blocked this turn if able.$Equipped creature gets +2/+1.$Equip {2}| +Ultima Weapon|Final Fantasy|563|R|{7}|Legendary Artifact - Equipment|||Whenever equipped creature attacks, destroy target creature an opponent controls.$Equipped creature gets +7/+7.$Equip {7}| +Plains|Final Fantasy|572|C||Basic Land - Plains|||({T}: Add {W}.)| +Island|Final Fantasy|573|C||Basic Land - Island|||({T}: Add {U}.)| +Swamp|Final Fantasy|574|C||Basic Land - Swamp|||({T}: Add {B}.)| +Mountain|Final Fantasy|575|C||Basic Land - Mountain|||({T}: Add {R}.)| +Forest|Final Fantasy|576|C||Basic Land - Forest|||({T}: Add {G}.)| +Y'shtola Rhul|Final Fantasy|577|M|{4}{U}{U}|Legendary Creature - Cat Druid|3|5|At the beginning of your end step, exile target creature you control, then return it to the battlefield under its owner's control. Then if it's the first end step of the turn, there is an additional end step after this step.| +Phoenix Down|Final Fantasy|578|U|{W}|Artifact|||{1}{W}, {T}, Exile this artifact: Choose one--$* Return target creature card with mana value 4 or less from your graveyard to the battlefield tapped.$* Exile target Skeleton, Spirit, or Zombie.| +White Auracite|Final Fantasy|579|C|{2}{W}{W}|Artifact|||When this artifact enters, exile target nonland permanent an opponent controls until this artifact leaves the battlefield.${T}: Add {W}.| +Zack Fair|Final Fantasy|580|U|{W}|Legendary Creature - Human Soldier|0|1|Zack Fair enters with a +1/+1 counter on it.${1}, Sacrifice Zack Fair: Target creature you control gains indestructible until end of turn. Put Zack Fair's counters on that creature and attach an Equipment that was attached to Zack Fair to that creature.| +Astrologian's Planisphere|Final Fantasy|581|R|{1}{U}|Artifact - Equipment|||Job select$Equipped creature is a Wizard in addition to its other types and has "Whenever you cast a noncreature spell and whenever you draw your third card each turn, put a +1/+1 counter on this creature."$Diana -- Equip {2}| +Sage's Nouliths|Final Fantasy|582|C|{1}{U}|Artifact - Equipment|||Job select$Equipped creature gets +1/+0, has "Whenever this creature attacks, untap target attacking creature," and is a Cleric in addition to its other types.$Hagneia -- Equip {3}| +Circle of Power|Final Fantasy|583|U|{3}{B}|Sorcery|||You draw two cards and you lose 2 life. Create a 0/1 black Wizard creature token with "Whenever you cast a noncreature spell, this token deals 1 damage to each opponent."$Wizards you control get +1/+0 and gain lifelink until end of turn.| +Barret Wallace|Final Fantasy|584|U|{3}{R}|Legendary Creature - Human Rebel|4|4|Reach$Whenever Barret Wallace attacks, it deals damage equal to the number of equipped creatures you control to defending player.| +Laughing Mad|Final Fantasy|585|C|{2}{R}|Instant|||As an additional cost to cast this spell, discard a card.$Draw two cards.$Flashback {3}{R}| Ugin, Eye of the Storms|Tarkir: Dragonstorm|1|M|{7}|Legendary Planeswalker - Ugin|7|When you cast this spell, exile up to one target permanent that's one or more colors.$Whenever you cast a colorless spell, exile up to one target permanent that's one or more colors.$+2: You gain 3 life and draw a card.$0: Add {C}{C}{C}.$-11: Search your library for any number of colorless nonland cards, exile them, then shuffle. Until end of turn, you may cast those cards without paying their mana costs.| Anafenza, Unyielding Lineage|Tarkir: Dragonstorm|2|R|{2}{W}|Legendary Creature - Spirit Soldier|2|2|Flash$First strike$Whenever another nontoken creature you control dies, Anafenza endures 2.| Arashin Sunshield|Tarkir: Dragonstorm|3|C|{3}{W}|Creature - Human Warrior|3|4|When this creature enters, exile up to two target cards from a single graveyard.${W}, {T}: Tap target creature.|