diff --git a/Mage.Client/pom.xml b/Mage.Client/pom.xml index 1fe29e95f4e..d702cf1ac96 100644 --- a/Mage.Client/pom.xml +++ b/Mage.Client/pom.xml @@ -151,13 +151,6 @@ 1.17 - - - - org.ocpsoft.prettytime - prettytime - 4.0.6.Final - diff --git a/Mage.Client/src/main/java/mage/client/table/PlayersChatPanel.java b/Mage.Client/src/main/java/mage/client/table/PlayersChatPanel.java index c93d929366d..1d0bb9e69cc 100644 --- a/Mage.Client/src/main/java/mage/client/table/PlayersChatPanel.java +++ b/Mage.Client/src/main/java/mage/client/table/PlayersChatPanel.java @@ -6,6 +6,8 @@ import mage.client.util.GUISizeHelper; import mage.client.util.MageTableRowSorter; import mage.client.util.gui.TableUtil; import mage.client.util.gui.countryBox.CountryCellRenderer; +import mage.components.table.MageTable; +import mage.components.table.TableInfo; import mage.remote.MageRemoteException; import mage.view.RoomUsersView; import mage.view.UsersView; diff --git a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java index b61b6569cb2..74e7ea765ae 100644 --- a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java +++ b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java @@ -13,6 +13,9 @@ import mage.client.util.MageTableRowSorter; import mage.client.util.URLHandler; import mage.client.util.gui.GuiDisplayUtil; import mage.client.util.gui.TableUtil; +import mage.components.table.MageTable; +import mage.components.table.TableInfo; +import mage.components.table.TimeAgoTableCellRenderer; import mage.constants.*; import mage.game.match.MatchOptions; import mage.players.PlayerType; @@ -155,19 +158,8 @@ public class TablesPanel extends javax.swing.JPanel { final JToggleButton[] filterButtons; - // time formater - private final PrettyTime timeFormater = new PrettyTime(Locale.ENGLISH); - - // time ago renderer - TableCellRenderer timeAgoCellRenderer = new DefaultTableCellRenderer() { - @Override - public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { - JLabel label = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); - Date d = (Date) value; - label.setText(timeFormater.format(d)); - return label; - } - }; + // time formatter + private final PrettyTime timeFormatter = new PrettyTime(Locale.ENGLISH); // duration renderer TableCellRenderer durationCellRenderer = new DefaultTableCellRenderer() { @@ -177,8 +169,8 @@ public class TablesPanel extends javax.swing.JPanel { Long ms = (Long) value; if (ms != 0) { - Duration dur = timeFormater.approximateDuration(new Date(ms)); - label.setText((timeFormater.formatDuration(dur))); + Duration dur = timeFormatter.approximateDuration(new Date(ms)); + label.setText((timeFormatter.formatDuration(dur))); } else { label.setText(""); } @@ -298,13 +290,8 @@ public class TablesPanel extends javax.swing.JPanel { initComponents(); // tableModel.setSession(session); - // formater - // change default just now from 60 to 30 secs - // see workaround for 4.0 versions: https://github.com/ocpsoft/prettytime/issues/152 - TimeFormat timeFormat = timeFormater.removeUnit(JustNow.class); - JustNow newJustNow = new JustNow(); - newJustNow.setMaxQuantity(1000L * 30L); // 30 seconds gap (show "just now" from 0 to 30 secs) - timeFormater.registerUnit(newJustNow, timeFormat); + // formatter + MageTable.fixTimeFormatter(this.timeFormatter); // 1. TABLE CURRENT tableTables.createDefaultColumnsFromModel(); @@ -334,7 +321,7 @@ public class TablesPanel extends javax.swing.JPanel { tableTables.setRowSorter(activeTablesSorter); // time ago - tableTables.getColumnModel().getColumn(TablesTableModel.COLUMN_CREATED).setCellRenderer(timeAgoCellRenderer); + tableTables.getColumnModel().getColumn(TablesTableModel.COLUMN_CREATED).setCellRenderer(TimeAgoTableCellRenderer.getInstance()); // skill level tableTables.getColumnModel().getColumn(TablesTableModel.COLUMN_SKILL).setCellRenderer(skillCellRenderer); // seats @@ -545,7 +532,7 @@ public class TablesPanel extends javax.swing.JPanel { table.getSelectionModel().addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { - int modelRow = TablesUtil.getSelectedModelRow(table); + int modelRow = MageTable.getSelectedModelRow(table); if (modelRow != -1) { // needs only selected String rowId = TablesUtil.getSearchIdFromTable(table, modelRow); @@ -563,7 +550,7 @@ public class TablesPanel extends javax.swing.JPanel { public void run() { String lastRowID = tablesLastSelection.get(table); int needModelRow = TablesUtil.findTableRowFromSearchId(table.getModel(), lastRowID); - int needViewRow = TablesUtil.getViewRowFromModel(table, needModelRow); + int needViewRow = MageTable.getViewRowFromModel(table, needModelRow); if (needViewRow != -1) { table.clearSelection(); table.addRowSelectionInterval(needViewRow, needViewRow); @@ -581,7 +568,7 @@ public class TablesPanel extends javax.swing.JPanel { if (!SwingUtilities.isLeftMouseButton(e)) { return; } - int modelRow = TablesUtil.getSelectedModelRow(table); + int modelRow = MageTable.getSelectedModelRow(table); if (e.getClickCount() == 2 && modelRow != -1) { action.actionPerformed(new ActionEvent(table, ActionEvent.ACTION_PERFORMED, TablesUtil.getSearchIdFromTable(table, modelRow))); } diff --git a/Mage.Client/src/main/java/mage/client/table/TablesTableModel.java b/Mage.Client/src/main/java/mage/client/table/TablesTableModel.java index 6fa3fb65690..3d78e2fa2e4 100644 --- a/Mage.Client/src/main/java/mage/client/table/TablesTableModel.java +++ b/Mage.Client/src/main/java/mage/client/table/TablesTableModel.java @@ -1,6 +1,7 @@ package mage.client.table; import mage.client.SessionHandler; +import mage.components.table.TableModelWithTooltip; import mage.constants.SkillLevel; import mage.remote.MageRemoteException; import mage.view.TableView; diff --git a/Mage.Client/src/main/java/mage/client/table/TablesUtil.java b/Mage.Client/src/main/java/mage/client/table/TablesUtil.java index 6fd2b8445ee..c8923a623f7 100644 --- a/Mage.Client/src/main/java/mage/client/table/TablesUtil.java +++ b/Mage.Client/src/main/java/mage/client/table/TablesUtil.java @@ -5,6 +5,8 @@ import org.apache.log4j.Logger; import javax.swing.*; /** + * GUI related + * * @author JayDi85 */ public class TablesUtil { @@ -42,22 +44,4 @@ public class TablesUtil { } return row; } - - public static int getSelectedModelRow(JTable table) { - return getModelRowFromView(table, table.getSelectedRow()); - } - - public static int getModelRowFromView(JTable table, int viewRow) { - if (viewRow != -1 && viewRow < table.getModel().getRowCount()) { - return table.convertRowIndexToModel(viewRow); - } - return -1; - } - - public static int getViewRowFromModel(JTable table, int modelRow) { - if (modelRow != -1 && modelRow < table.getModel().getRowCount()) { - return table.convertRowIndexToView(modelRow); - } - return -1; - } } 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 c01006ee9d1..48472f77fb5 100644 --- a/Mage.Client/src/main/java/mage/client/util/GUISizeHelper.java +++ b/Mage.Client/src/main/java/mage/client/util/GUISizeHelper.java @@ -186,13 +186,6 @@ public final class GUISizeHelper { } } - public static String textToHtmlWithSize(String text, Font font) { - if (text != null && !text.toLowerCase(Locale.ENGLISH).startsWith("")) { - return "

" + text + "

"; - } - return text; - } - /** * Return scrollbar settings, so user can scroll it more faster for bigger cards * diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSets.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSets.java index bb59cc2bdfa..957aaf4e0b5 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSets.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSets.java @@ -97,13 +97,14 @@ public class GathererSets implements Iterable { "GNT", "UMA", "GRN", "RNA", "WAR", "MH1", "M20", - "C19","ELD","MB1","GN2","J20","THB","UND","C20","IKO","M21", - "JMP","2XM","ZNR","KLR","CMR","KHC","KHM","TSR","STX","STA", - "C21","MH2","AFR","AFC","J21","MID","MIC","VOW","VOC","YMID", - "NEC","NEO","SNC","NCC","CLB","2X2","DMU","DMC","40K","GN3", - "UNF","BRO","BRC","BOT","30A","J22","SCD","DMR","ONE","ONC", - "MOM","MOC","MUL","MAT","LTR","CMM","WOE","WHO","RVR","WOT", - "WOC","SPG","LCI","LCC","REX" + "C19", "ELD", "MB1", "GN2", "J20", "THB", "UND", "C20", "IKO", "M21", + "JMP", "2XM", "ZNR", "KLR", "CMR", "KHC", "KHM", "TSR", "STX", "STA", + "C21", "MH2", "AFR", "AFC", "J21", "MID", "MIC", "VOW", "VOC", "YMID", + "NEC", "NEO", "SNC", "NCC", "CLB", "2X2", "DMU", "DMC", "40K", "GN3", + "UNF", "BRO", "BRC", "BOT", "30A", "J22", "SCD", "DMR", "ONE", "ONC", + "MOM", "MOC", "MUL", "MAT", "LTR", "CMM", "WOE", "WHO", "RVR", "WOT", + "WOC", "SPG", "LCI", "LCC", "REX", "PIP", "MKM", "MKC", "CLU", "OTJ", + "OTC", "OTP", "BIG", "MH3", "ACR", "BLB" // "HHO", "ANA" -- do not exist on gatherer }; diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportCards.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportCards.java index e670880fba1..a8209ba767a 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportCards.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportCards.java @@ -547,6 +547,8 @@ public class ScryfallImageSupportCards { add("MKC"); // Murders at Karlov Manor Commander add("CLU"); // Ravnica: Clue Edition add("OTJ"); // Outlaws of Thunder Junction + add("OTC"); // Outlaws of Thunder Junction Commander + add("OTP"); // Breaking News add("BIG"); // The Big Score add("MH3"); // Modern Horizons 3 add("ACR"); // Assassin's Creed diff --git a/Mage.Common/pom.xml b/Mage.Common/pom.xml index dfa8dea5840..d7f33c9c06a 100644 --- a/Mage.Common/pom.xml +++ b/Mage.Common/pom.xml @@ -43,6 +43,13 @@ 4.2.2.GA + + + org.ocpsoft.prettytime + prettytime + 4.0.6.Final + + concurrent diff --git a/Mage.Client/src/main/java/mage/client/table/ColumnInfo.java b/Mage.Common/src/main/java/mage/components/table/ColumnInfo.java similarity index 96% rename from Mage.Client/src/main/java/mage/client/table/ColumnInfo.java rename to Mage.Common/src/main/java/mage/components/table/ColumnInfo.java index fcd9aed3155..6ca06c2b186 100644 --- a/Mage.Client/src/main/java/mage/client/table/ColumnInfo.java +++ b/Mage.Common/src/main/java/mage/components/table/ColumnInfo.java @@ -1,4 +1,4 @@ -package mage.client.table; +package mage.components.table; /** * @author JayDi85 diff --git a/Mage.Client/src/main/java/mage/client/table/MageTable.java b/Mage.Common/src/main/java/mage/components/table/MageTable.java similarity index 57% rename from Mage.Client/src/main/java/mage/client/table/MageTable.java rename to Mage.Common/src/main/java/mage/components/table/MageTable.java index f2ed79e19f1..c137ed35105 100644 --- a/Mage.Client/src/main/java/mage/client/table/MageTable.java +++ b/Mage.Common/src/main/java/mage/components/table/MageTable.java @@ -1,13 +1,17 @@ -package mage.client.table; +package mage.components.table; -import mage.client.util.GUISizeHelper; import org.apache.log4j.Logger; +import org.ocpsoft.prettytime.PrettyTime; +import org.ocpsoft.prettytime.TimeFormat; +import org.ocpsoft.prettytime.units.JustNow; import javax.swing.*; import javax.swing.table.JTableHeader; import javax.swing.table.TableColumn; import javax.swing.table.TableModel; +import java.awt.*; import java.awt.event.MouseEvent; +import java.util.Locale; /** * GUI: basic mage table for any data like game tables list, players list, etc @@ -26,7 +30,7 @@ public class MageTable extends JTable { public MageTable(TableInfo tableInfo) { this.tableInfo = tableInfo; } - + public void setTableInfo(TableInfo tableInfo) { this.tableInfo = tableInfo; } @@ -37,7 +41,7 @@ public class MageTable extends JTable { java.awt.Point p = e.getPoint(); int viewRow = rowAtPoint(p); int viewCol = columnAtPoint(p); - int modelRow = TablesUtil.getModelRowFromView(this, viewRow); + int modelRow = getModelRowFromView(this, viewRow); int modelCol = this.convertColumnIndexToModel(viewCol); String tip = null; if (modelRow != -1 && modelCol != -1) { @@ -48,7 +52,7 @@ public class MageTable extends JTable { tip = model.getValueAt(modelRow, modelCol).toString(); } } - return GUISizeHelper.textToHtmlWithSize(tip, GUISizeHelper.tableFont); + return textToHtmlWithSize(tip, this.getFont()); } @Override @@ -78,8 +82,43 @@ public class MageTable extends JTable { tip = col.getHeaderValue().toString(); } - return GUISizeHelper.textToHtmlWithSize(tip, GUISizeHelper.tableFont); + return textToHtmlWithSize(tip, MageTable.this.getFont()); } }; } + + public static int getSelectedModelRow(JTable table) { + return getModelRowFromView(table, table.getSelectedRow()); + } + + public static int getModelRowFromView(JTable table, int viewRow) { + if (viewRow != -1 && viewRow < table.getModel().getRowCount()) { + return table.convertRowIndexToModel(viewRow); + } + return -1; + } + + public static int getViewRowFromModel(JTable table, int modelRow) { + if (modelRow != -1 && modelRow < table.getModel().getRowCount()) { + return table.convertRowIndexToView(modelRow); + } + return -1; + } + + public static String textToHtmlWithSize(String text, Font font) { + if (text != null && !text.toLowerCase(Locale.ENGLISH).startsWith("")) { + return "

" + text + "

"; + } + return text; + } + + public static void fixTimeFormatter(PrettyTime timeFormatter) { + // TODO: remove after PrettyTime lib upgrade to v5 + // change default just now from 60 to 30 secs + // see workaround for 4.0 versions: https://github.com/ocpsoft/prettytime/issues/152 + TimeFormat timeFormat = timeFormatter.removeUnit(JustNow.class); + JustNow newJustNow = new JustNow(); + newJustNow.setMaxQuantity(1000L * 30L); // 30 seconds gap (show "just now" from 0 to 30 secs) + timeFormatter.registerUnit(newJustNow, timeFormat); + } } diff --git a/Mage.Client/src/main/java/mage/client/table/TableInfo.java b/Mage.Common/src/main/java/mage/components/table/TableInfo.java similarity index 97% rename from Mage.Client/src/main/java/mage/client/table/TableInfo.java rename to Mage.Common/src/main/java/mage/components/table/TableInfo.java index d1ad44cd78e..65ed23215aa 100644 --- a/Mage.Client/src/main/java/mage/client/table/TableInfo.java +++ b/Mage.Common/src/main/java/mage/components/table/TableInfo.java @@ -1,4 +1,4 @@ -package mage.client.table; +package mage.components.table; import java.util.ArrayList; import java.util.List; diff --git a/Mage.Client/src/main/java/mage/client/table/TableModelWithTooltip.java b/Mage.Common/src/main/java/mage/components/table/TableModelWithTooltip.java similarity index 65% rename from Mage.Client/src/main/java/mage/client/table/TableModelWithTooltip.java rename to Mage.Common/src/main/java/mage/components/table/TableModelWithTooltip.java index 6d1de4b060b..4f111a117b3 100644 --- a/Mage.Client/src/main/java/mage/client/table/TableModelWithTooltip.java +++ b/Mage.Common/src/main/java/mage/components/table/TableModelWithTooltip.java @@ -1,7 +1,9 @@ -package mage.client.table; +package mage.components.table; /** * GUI: add support of tooltip/hint for table's cells on mouse move (used by MageTable) + *

+ * Make sure form and java files uses new MageTable(), not new JTable() code * * @author JayDi85 */ diff --git a/Mage.Common/src/main/java/mage/components/table/TimeAgoTableCellRenderer.java b/Mage.Common/src/main/java/mage/components/table/TimeAgoTableCellRenderer.java new file mode 100644 index 00000000000..2d712b589ed --- /dev/null +++ b/Mage.Common/src/main/java/mage/components/table/TimeAgoTableCellRenderer.java @@ -0,0 +1,41 @@ +package mage.components.table; + +import org.ocpsoft.prettytime.PrettyTime; + +import javax.swing.*; +import javax.swing.table.DefaultTableCellRenderer; +import java.awt.*; +import java.util.Date; +import java.util.Locale; + +/** + * GUI: create time ago cell renderer for date values in the table's cell + *

+ * Usage example: + * tableTables.getColumnModel().getColumn(TablesTableModel.COLUMN_CREATED).setCellRenderer(TimeAgoTableCellRenderer.getInstance()); + * + * @author JayDi85 + */ +public class TimeAgoTableCellRenderer extends DefaultTableCellRenderer { + + static final PrettyTime timeFormatter; + + static { + timeFormatter = new PrettyTime(Locale.ENGLISH); + MageTable.fixTimeFormatter(timeFormatter); + } + + private static final TimeAgoTableCellRenderer instance = new TimeAgoTableCellRenderer(); + + public static TimeAgoTableCellRenderer getInstance() { + return instance; + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + JLabel label = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + Date d = (Date) value; + label.setText(timeFormatter.format(d)); + return label; + } +} diff --git a/Mage.Server.Console/src/main/java/mage/server/console/ConnectDialog.java b/Mage.Server.Console/src/main/java/mage/server/console/ConnectDialog.java index caf262e0ff7..b0046e18c54 100644 --- a/Mage.Server.Console/src/main/java/mage/server/console/ConnectDialog.java +++ b/Mage.Server.Console/src/main/java/mage/server/console/ConnectDialog.java @@ -32,6 +32,7 @@ public class ConnectDialog extends JDialog { public ConnectDialog() { initComponents(); cbProxyType.setModel(new DefaultComboBoxModel(Connection.ProxyType.values())); + setVisible(false); } public void showDialog(ConsoleFrame console) { @@ -39,8 +40,8 @@ public class ConnectDialog extends JDialog { this.txtServer.setText(ConsoleFrame.getPreferences().get("serverAddress", "localhost")); this.txtPort.setText(ConsoleFrame.getPreferences().get("serverPort", Integer.toString(17171))); this.chkAutoConnect.setSelected(Boolean.parseBoolean(ConsoleFrame.getPreferences().get("autoConnect", "false"))); - this.txtProxyServer.setText(ConsoleFrame.getPreferences().get("proxyAddress", "localhost")); - this.txtProxyPort.setText(ConsoleFrame.getPreferences().get("proxyPort", Integer.toString(17171))); + this.txtProxyServer.setText(ConsoleFrame.getPreferences().get("proxyAddress", "")); + this.txtProxyPort.setText(ConsoleFrame.getPreferences().get("proxyPort", Integer.toString(0))); this.cbProxyType.setSelectedItem(Connection.ProxyType.valueOf(ConsoleFrame.getPreferences().get("proxyType", "NONE").toUpperCase(Locale.ENGLISH))); this.txtProxyUserName.setText(ConsoleFrame.getPreferences().get("proxyUsername", "")); this.txtPasswordField.setText(ConsoleFrame.getPreferences().get("proxyPassword", "")); @@ -71,7 +72,16 @@ public class ConnectDialog extends JDialog { private void saveSettings() { ConsoleFrame.getPreferences().put("serverAddress", txtServer.getText()); ConsoleFrame.getPreferences().put("serverPort", txtPort.getText()); - ConsoleFrame.getPreferences().put("autoConnect", Boolean.toString(chkAutoConnect.isSelected())); + if (chkAutoConnect.isSelected()) { + ConsoleFrame.getPreferences().putBoolean("autoConnect", true); + char[] input = txtPassword.getPassword(); + ConsoleFrame.getPreferences().put("password", new String(input)); + Arrays.fill(input, '0'); + } else { + ConsoleFrame.getPreferences().putBoolean("autoConnect", false); + ConsoleFrame.getPreferences().put("password", ""); + } + ConsoleFrame.getPreferences().put("proxyAddress", txtProxyServer.getText()); ConsoleFrame.getPreferences().put("proxyPort", txtProxyPort.getText()); ConsoleFrame.getPreferences().put("proxyType", cbProxyType.getSelectedItem().toString()); diff --git a/Mage.Server.Console/src/main/java/mage/server/console/ConsoleFrame.java b/Mage.Server.Console/src/main/java/mage/server/console/ConsoleFrame.java index 6be2097ec43..76131c32734 100644 --- a/Mage.Server.Console/src/main/java/mage/server/console/ConsoleFrame.java +++ b/Mage.Server.Console/src/main/java/mage/server/console/ConsoleFrame.java @@ -11,6 +11,7 @@ import org.apache.log4j.Logger; import javax.swing.*; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; +import java.util.Locale; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -63,6 +64,13 @@ public class ConsoleFrame extends javax.swing.JFrame implements MageClient { UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel"); session = new SessionImpl(this); connectDialog = new ConnectDialog(); + + // try auto connect + if (!autoConnect()) { + SwingUtilities.invokeLater(() -> { + connectDialog.showDialog(this); + }); + } } catch (Exception ex) { logger.fatal("", ex); } @@ -78,6 +86,29 @@ public class ConsoleFrame extends javax.swing.JFrame implements MageClient { return false; } + public boolean autoConnect() { + boolean needAutoConnect = Boolean.parseBoolean(ConsoleFrame.getPreferences().get("autoConnect", "false")); + boolean status = false; + if (needAutoConnect) { + String server = ConsoleFrame.getPreferences().get("serverAddress", "localhost"); + logger.info("Auto-connecting to " + server); + Connection newConnection = new Connection(); + newConnection.setHost(server); + newConnection.setPort(ConsoleFrame.getPreferences().getInt("serverPort", 17171)); + newConnection.setUsername(SessionImpl.ADMIN_NAME); + newConnection.setAdminPassword(ConsoleFrame.getPreferences().get("password", "")); + newConnection.setProxyType(Connection.ProxyType.valueOf(ConsoleFrame.getPreferences().get("proxyType", "NONE").toUpperCase(Locale.ENGLISH))); + if (!newConnection.getProxyType().equals(Connection.ProxyType.NONE)) { + newConnection.setProxyHost(ConsoleFrame.getPreferences().get("proxyAddress", "")); + newConnection.setProxyPort(ConsoleFrame.getPreferences().getInt("proxyPort", 0)); + newConnection.setProxyUsername(ConsoleFrame.getPreferences().get("proxyUsername", "")); + newConnection.setProxyPassword(ConsoleFrame.getPreferences().get("proxyPassword", "")); + } + status = connect(newConnection); + } + return status; + } + public void setStatusText(String status) { this.lblStatus.setText(status); } diff --git a/Mage.Server.Console/src/main/java/mage/server/console/ConsolePanel.form b/Mage.Server.Console/src/main/java/mage/server/console/ConsolePanel.form index 6830ef137b6..b430478292b 100644 --- a/Mage.Server.Console/src/main/java/mage/server/console/ConsolePanel.form +++ b/Mage.Server.Console/src/main/java/mage/server/console/ConsolePanel.form @@ -88,6 +88,9 @@ + + + @@ -262,6 +265,9 @@ + + + diff --git a/Mage.Server.Console/src/main/java/mage/server/console/ConsolePanel.java b/Mage.Server.Console/src/main/java/mage/server/console/ConsolePanel.java index 63c9e2e72e6..f27f03f6ba8 100644 --- a/Mage.Server.Console/src/main/java/mage/server/console/ConsolePanel.java +++ b/Mage.Server.Console/src/main/java/mage/server/console/ConsolePanel.java @@ -1,5 +1,8 @@ package mage.server.console; + import mage.components.table.MageTable; + import mage.components.table.TableModelWithTooltip; + import mage.components.table.TimeAgoTableCellRenderer; import mage.remote.Session; import mage.view.TableView; import mage.view.UserView; @@ -13,6 +16,7 @@ import java.util.*; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; + import java.util.stream.Collectors; import static javax.swing.JTable.AUTO_RESIZE_NEXT_COLUMN; import static javax.swing.JTable.AUTO_RESIZE_OFF; @@ -36,6 +40,7 @@ this.tableUserModel = new TableUserModel(); this.tableTableModel = new TableTableModel(); initComponents(); + spinnerMuteDurationMinutes.setValue(60); this.tblUsers.createDefaultColumnsFromModel(); this.tblUsers.setRowSorter(new TableRowSorter(tableUserModel)); @@ -43,7 +48,8 @@ this.tblTables.createDefaultColumnsFromModel(); this.tblTables.setRowSorter(new TableRowSorter(tableTableModel)); - this.tblUsers.setAutoResizeMode(AUTO_RESIZE_NEXT_COLUMN); + this.tblTables.setAutoResizeMode(AUTO_RESIZE_NEXT_COLUMN); + this.tblTables.getColumnModel().getColumn(TableTableModel.COLUMN_CREATED).setCellRenderer(TimeAgoTableCellRenderer.getInstance()); } public void update(List users) { @@ -93,7 +99,7 @@ jPanel1 = new javax.swing.JPanel(); jPanel3 = new javax.swing.JPanel(); jScrollPane1 = new javax.swing.JScrollPane(); - tblUsers = new javax.swing.JTable(); + tblUsers = new MageTable(); jPanel4 = new javax.swing.JPanel(); btnDisconnect = new javax.swing.JButton(); btnEndSession = new javax.swing.JButton(); @@ -105,7 +111,7 @@ jPanel2 = new javax.swing.JPanel(); jPanel5 = new javax.swing.JPanel(); jScrollPane2 = new javax.swing.JScrollPane(); - tblTables = new javax.swing.JTable(); + tblTables = new MageTable(); jPanel6 = new javax.swing.JPanel(); btnRemoveTable = new javax.swing.JButton(); jUserName = new javax.swing.JTextField(); @@ -373,7 +379,10 @@ private void btnRemoveTableActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnRemoveTableActionPerformed int row = this.tblTables.convertRowIndexToModel(tblTables.getSelectedRow()); - ConsoleFrame.getSession().removeTable((UUID) tableTableModel.getValueAt(row, 7)); + if (row >= 0) { + TableView tableView = this.tableTableModel.getTableView(row); + ConsoleFrame.getSession().removeTable(tableView.getTableId()); + } }//GEN-LAST:event_btnRemoveTableActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables @@ -484,9 +493,11 @@ } - class TableTableModel extends AbstractTableModel { + class TableTableModel extends AbstractTableModel implements TableModelWithTooltip { - private final String[] columnNames = new String[]{"Table Name", "Owner", "Game Type", "Deck Type", "Status"}; + protected static final int COLUMN_CREATED = 4; + + private final String[] columnNames = new String[]{"Table name", "Players", "Game type", "Deck type", "Created", "Status", "Games"}; private TableView[] tables = new TableView[0]; public void loadData(Collection tables) { @@ -515,17 +526,20 @@ return tables[arg0].getGameType(); case 3: return tables[arg0].getDeckType(); - case 4: - return tables[arg0].getTableState().toString(); + case COLUMN_CREATED: + return tables[arg0].getCreateTime(); case 5: - return tables[arg0].isTournament(); - case 6: - if (!tables[arg0].getGames().isEmpty()) { - return tables[arg0].getGames().get(0); + return tables[arg0].getTableStateText(); + case 6:{ + if (tables[arg0].getGames().isEmpty()) { + return "NO GAMES"; + } else if (tables[arg0].getGames().size() == 1) { + return tables[arg0].getGames().get(0).toString(); + } else { + return String.format("%d games:", tables[arg0].getGames().size()) + + "
" + tables[arg0].getGames().stream().map(UUID::toString).collect(Collectors.joining("
")); } - return null; - case 7: - return tables[arg0].getTableId(); + } } return ""; } @@ -543,14 +557,31 @@ @Override public Class getColumnClass(int columnIndex) { - return String.class; + if (columnIndex == COLUMN_CREATED) { + return Date.class; + } else { + return String.class; + } } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { - return columnIndex == 5; + return false; } + public TableView getTableView(int row) { + if (row >= 0 && row <= this.tables.length - 1) { + return this.tables[row]; + } else { + throw new IllegalArgumentException("Unknown table row: " + row); + } + } + + @Override + public String getTooltipAt(int rowIndex, int columnIndex) { + Object res = this.getValueAt(rowIndex, columnIndex); + return res == null ? null : res.toString(); + } } class UpdateUsersTask extends SwingWorker> { diff --git a/Mage.Server/src/main/java/mage/server/Session.java b/Mage.Server/src/main/java/mage/server/Session.java index 87d4e4c24a1..e9cb6e1476f 100644 --- a/Mage.Server/src/main/java/mage/server/Session.java +++ b/Mage.Server/src/main/java/mage/server/Session.java @@ -449,8 +449,10 @@ public class Session { logger.warn("SESSION LOCK, possible connection problem - fireCallback - userId: " + userId + " messageId: " + call.getMessageId(), ex); } } catch (HandleCallbackException ex) { - // something wrong, maybe connection problem - logger.warn("SESSION CALLBACK EXCEPTION - " + ThreadUtils.findRootException(ex) + ", userId " + userId + ", messageId: " + call.getMessageId(), ex); + // general error + // can raise on server freeze or normal connection problem from a client side + // no need to print a full stack log here + logger.warn("SESSION CALLBACK EXCEPTION - " + ThreadUtils.findRootException(ex) + ", userId " + userId + ", messageId: " + call.getMessageId()); // do not send data anymore (user must reconnect) this.valid = false; diff --git a/Mage.Sets/src/mage/cards/a/AbradedBluffs.java b/Mage.Sets/src/mage/cards/a/AbradedBluffs.java new file mode 100644 index 00000000000..40f86e89e57 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AbradedBluffs.java @@ -0,0 +1,48 @@ +package mage.cards.a; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTappedAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.DamageTargetEffect; +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 mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AbradedBluffs extends CardImpl { + + public AbradedBluffs(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + this.subtype.add(SubType.DESERT); + + // Abraded Bluffs enters the battlefield tapped. + this.addAbility(new EntersBattlefieldTappedAbility()); + + // When Abraded Bluffs enters the battlefield, it deals 1 damage to target opponent. + Ability ability = new EntersBattlefieldTriggeredAbility(new DamageTargetEffect(1, "it")); + ability.addTarget(new TargetOpponent()); + this.addAbility(ability); + + // {T}: Add {R} or {W}. + this.addAbility(new RedManaAbility()); + this.addAbility(new WhiteManaAbility()); + } + + private AbradedBluffs(final AbradedBluffs card) { + super(card); + } + + @Override + public AbradedBluffs copy() { + return new AbradedBluffs(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AegarTheFreezingFlame.java b/Mage.Sets/src/mage/cards/a/AegarTheFreezingFlame.java index 1a80e15bd5b..ab3c5d2ed06 100644 --- a/Mage.Sets/src/mage/cards/a/AegarTheFreezingFlame.java +++ b/Mage.Sets/src/mage/cards/a/AegarTheFreezingFlame.java @@ -9,6 +9,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.game.Game; +import mage.game.events.DamagedBatchForOnePermanentEvent; import mage.game.events.DamagedEvent; import mage.game.events.GameEvent; import mage.watchers.Watcher; @@ -56,14 +57,22 @@ class AegarTheFreezingFlameTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT; } @Override public boolean checkTrigger(GameEvent event, Game game) { - DamagedEvent dEvent = (DamagedEvent) event; - if (dEvent.getExcess() < 1 - || !game.getOpponents(getControllerId()).contains(game.getControllerId(event.getTargetId()))) { + DamagedBatchForOnePermanentEvent dEvent = (DamagedBatchForOnePermanentEvent) event; + + int excess = dEvent.getEvents() + .stream() + .mapToInt(DamagedEvent::getExcess) + .sum(); + + boolean controlledByOpponent = + game.getOpponents(getControllerId()).contains(game.getControllerId(event.getTargetId())); + + if (excess < 1 || !controlledByOpponent) { return false; } AegarTheFreezingFlameWatcher watcher = game.getState().getWatcher(AegarTheFreezingFlameWatcher.class); diff --git a/Mage.Sets/src/mage/cards/a/AetherSnap.java b/Mage.Sets/src/mage/cards/a/AetherSnap.java index 6687234022c..7e159e6096a 100644 --- a/Mage.Sets/src/mage/cards/a/AetherSnap.java +++ b/Mage.Sets/src/mage/cards/a/AetherSnap.java @@ -63,7 +63,7 @@ class AetherSnapEffect extends OneShotEffect { return false; } Cards tokens = new CardsImpl(); - for (Permanent permanent : game.getBattlefield().getActivePermanents(source.getSourceId(), game)) { + for (Permanent permanent : game.getBattlefield().getActivePermanents(source.getControllerId(), game)) { if (permanent instanceof PermanentToken) { tokens.add(permanent); } diff --git a/Mage.Sets/src/mage/cards/a/AetherworksMarvel.java b/Mage.Sets/src/mage/cards/a/AetherworksMarvel.java index b88be179cef..575bf9cc12c 100644 --- a/Mage.Sets/src/mage/cards/a/AetherworksMarvel.java +++ b/Mage.Sets/src/mage/cards/a/AetherworksMarvel.java @@ -60,7 +60,7 @@ class AetherworksMarvelEffect extends OneShotEffect { AetherworksMarvelEffect() { super(Outcome.PlayForFree); this.staticText = "Look at the top six cards of your library. " - + "You may cast a card from among them without paying " + + "You may cast a spell from among them without paying " + "its mana cost. Put the rest on the bottom of your " + "library in a random order"; } diff --git a/Mage.Sets/src/mage/cards/a/AgencyOutfitter.java b/Mage.Sets/src/mage/cards/a/AgencyOutfitter.java new file mode 100644 index 00000000000..e6d1980d5b2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AgencyOutfitter.java @@ -0,0 +1,135 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.*; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.filter.predicate.mageobject.NamePredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardAndOrCard; +import mage.target.common.TargetCardAndOrCardInLibrary; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author notgreat + */ +public final class AgencyOutfitter extends CardImpl { + + public AgencyOutfitter(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{U}{U}"); + this.subtype.add(SubType.SPHINX); + this.subtype.add(SubType.DETECTIVE); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When Agency Outfitter enters the battlefield, you may search your graveyard, hand, and/or library for a card named Magnifying Glass and/or a card named Thinking Cap and put them onto the battlefield. If you search your library this way, shuffle. + this.addAbility(new EntersBattlefieldTriggeredAbility(new AgencyOutfitterEffect(), true)); + } + + private AgencyOutfitter(final AgencyOutfitter card) { + super(card); + } + + @Override + public AgencyOutfitter copy() { + return new AgencyOutfitter(this); + } +} + +class AgencyOutfitterEffect extends OneShotEffect { + private static final String glassName = "Magnifying Glass"; + private static final String capName = "Thinking Cap"; + + AgencyOutfitterEffect() { + super(Outcome.UnboostCreature); + this.staticText = "you may search your graveyard, hand, and/or library for a card named Magnifying Glass and/or a card named Thinking Cap and put them onto the battlefield. If you search your library this way, shuffle."; + } + + private AgencyOutfitterEffect(final AgencyOutfitterEffect effect) { + super(effect); + } + + @Override + public AgencyOutfitterEffect copy() { + return new AgencyOutfitterEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + Card glassCard = null; + Card capCard = null; + if (controller.chooseUse(Outcome.Neutral, "Search your library?", source, game)) { + TargetCardAndOrCardInLibrary libraryTarget = new TargetCardAndOrCardInLibrary(glassName, capName); + if (controller.searchLibrary(libraryTarget, source, game)) { + for (UUID id : libraryTarget.getTargets()) { + Card card = game.getCard(id); + if (card != null) { + if (CardUtil.haveSameNames(card, glassName, game)) { + glassCard = card; + } else if (CardUtil.haveSameNames(card, capName, game)) { + capCard = card; + } + } + } + } + controller.shuffleLibrary(source, game); + } + + if (glassCard == null || capCard == null) { + FilterCard filter; + TargetCard target; + if (glassCard == null && capCard == null) { + target = new TargetCardAndOrCard(glassName, capName); + filter = target.getFilter(); + } else { + String name = (glassCard == null ? glassName : capName); + filter = new FilterCard(); + filter.add(new NamePredicate(name)); + target = new TargetCard(0, 1, Zone.ALL, filter); + } + target.withNotTarget(true); + Cards cards = new CardsImpl(); + cards.addAllCards(controller.getHand().getCards(filter, source.getControllerId(), source, game)); + cards.addAllCards(controller.getGraveyard().getCards(filter, source.getControllerId(), source, game)); + if (!cards.isEmpty()) { + controller.choose(outcome, cards, target, source, game); + for (UUID id : target.getTargets()) { + Card card = game.getCard(id); + if (card != null) { + if (CardUtil.haveSameNames(card, glassName, game)) { + glassCard = card; + } else if (CardUtil.haveSameNames(card, capName, game)) { + capCard = card; + } + } + } + } + } + + Cards foundCards = new CardsImpl(); + foundCards.add(glassCard); + foundCards.add(capCard); + if (!foundCards.isEmpty()) { + controller.moveCards(foundCards, Zone.BATTLEFIELD, source, game); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/a/AgrusKosEternalSoldier.java b/Mage.Sets/src/mage/cards/a/AgrusKosEternalSoldier.java index 282cb292d6b..3789ce55f66 100644 --- a/Mage.Sets/src/mage/cards/a/AgrusKosEternalSoldier.java +++ b/Mage.Sets/src/mage/cards/a/AgrusKosEternalSoldier.java @@ -94,7 +94,7 @@ class AgrusKosEternalSoldierTriggeredAbility extends TriggeredAbilityImpl { if (targetingObject == null || targetingObject instanceof Spell) { return false; } - if (CardUtil.checkTargetedEventAlreadyUsed(this.id.toString(), targetingObject, event, game)) { + if (CardUtil.checkTargetedEventAlreadyUsed(this.getId().toString(), targetingObject, event, game)) { return false; } Set targets = targetingObject diff --git a/Mage.Sets/src/mage/cards/a/AjaniNacatlAvenger.java b/Mage.Sets/src/mage/cards/a/AjaniNacatlAvenger.java new file mode 100644 index 00000000000..ca79322f610 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AjaniNacatlAvenger.java @@ -0,0 +1,192 @@ +package mage.cards.a; + +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.abilities.LoyaltyAbility; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.dynamicvalue.common.CreaturesYouControlCount; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +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.common.FilterNonlandPermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.filter.predicate.mageobject.ColorPredicate; +import mage.filter.predicate.permanent.ControllerIdPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.CatWarrior21Token; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.common.TargetAnyTarget; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author Susucr + */ +public final class AjaniNacatlAvenger extends CardImpl { + + private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(SubType.CAT, "Cat you control"); + + public AjaniNacatlAvenger(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, ""); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.AJANI); + this.setStartingLoyalty(3); + + this.color.setRed(true); + this.color.setWhite(true); + this.nightCard = true; + + // +2: Put a +1/+1 counter on each Cat you control. + this.addAbility(new LoyaltyAbility( + new AddCountersAllEffect(CounterType.P1P1.createInstance(), filter), 2 + )); + + // 0: Create a 2/1 white Car Warrior creature token. When you do, if you control a red permanent other than Ajani, Nacatl Avenger, he deals damage equal to the number of creatures you control to any target. + this.addAbility(new LoyaltyAbility(new AjaniNacatlAvengerZeroEffect(), 0)); + + // -4: Each opponent chooses an artifact, a creature, an enchantment and a planeswalker from among the nonland permanents they control, then sacrifices the rest. + this.addAbility(new LoyaltyAbility(new AjaniNacatlAvengerMinusFourEffect(), -4)); + } + + private AjaniNacatlAvenger(final AjaniNacatlAvenger card) { + super(card); + } + + @Override + public AjaniNacatlAvenger copy() { + return new AjaniNacatlAvenger(this); + } +} + +class AjaniNacatlAvengerZeroEffect extends OneShotEffect { + + private static final FilterPermanent filter = new FilterPermanent("red permanent other than {this}"); + + static { + filter.add(new ColorPredicate(ObjectColor.RED)); + filter.add(AnotherPredicate.instance); + } + + private static final Condition condition = new PermanentsOnTheBattlefieldCondition(filter, true); + + AjaniNacatlAvengerZeroEffect() { + super(Outcome.PutCreatureInPlay); + staticText = "Create a 2/1 white Car Warrior creature token. " + + "When you do, if you control a red permanent other than {this}, " + + "he deals damage equal to the number of creatures you control to any target."; + } + + private AjaniNacatlAvengerZeroEffect(final AjaniNacatlAvengerZeroEffect effect) { + super(effect); + } + + @Override + public AjaniNacatlAvengerZeroEffect copy() { + return new AjaniNacatlAvengerZeroEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + if (!new CreateTokenEffect(new CatWarrior21Token()).apply(game, source)) { + return false; + } + + ReflexiveTriggeredAbility reflexive = new ReflexiveTriggeredAbility( + new DamageTargetEffect(CreaturesYouControlCount.instance), + false, + "When you do, if you control a red permanent other than {this}, " + + "he deals damage equal to the number of creatures you control to any target.", + condition + ); + reflexive.addTarget(new TargetAnyTarget()); + game.fireReflexiveTriggeredAbility(reflexive, source); + return true; + } +} + +// Inspired by Mythos of Snapdax +class AjaniNacatlAvengerMinusFourEffect extends OneShotEffect { + + private static final List cardTypes = Arrays.asList( + CardType.ARTIFACT, + CardType.CREATURE, + CardType.ENCHANTMENT, + CardType.PLANESWALKER + ); + + AjaniNacatlAvengerMinusFourEffect() { + super(Outcome.Benefit); + staticText = "Each opponent chooses an artifact, a creature, an enchantment and a planeswalker " + + "from among the nonland permanents they control, then sacrifices the rest."; + } + + private AjaniNacatlAvengerMinusFourEffect(final AjaniNacatlAvengerMinusFourEffect effect) { + super(effect); + } + + @Override + public AjaniNacatlAvengerMinusFourEffect copy() { + return new AjaniNacatlAvengerMinusFourEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + + List playerList = game + .getState() + .getPlayersInRange(source.getControllerId(), game) + .stream() + .map(game::getPlayer) + .filter(Objects::nonNull) + .filter(player -> controller.hasOpponent(player.getId(), game)) + .collect(Collectors.toList()); + + Set toKeep = new HashSet(); + for (Player player : playerList) { + for (CardType cardType : cardTypes) { + String message = cardType.toString().equals("Artifact") ? "an " : "a "; + message += cardType.toString().toLowerCase(Locale.ENGLISH); + FilterPermanent filter = new FilterNonlandPermanent(message); + filter.add(cardType.getPredicate()); + filter.add(new ControllerIdPredicate(player.getId())); + if (game.getBattlefield().count(filter, source.getControllerId(), source, game) == 0) { + continue; + } + TargetPermanent target = new TargetPermanent(filter); + target.withNotTarget(true); + player.choose(outcome, target, source, game); + toKeep.add(target.getFirstTarget()); + } + } + + for (Permanent permanent : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_NON_LAND, source.getControllerId(), game)) { + if (permanent == null || toKeep.contains(permanent.getId()) || !controller.hasOpponent(permanent.getControllerId(), game)) { + continue; + } + permanent.sacrifice(source, game); + } + return true; + } + +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/a/AjaniNacatlPariah.java b/Mage.Sets/src/mage/cards/a/AjaniNacatlPariah.java new file mode 100644 index 00000000000..dc9a8e0b29c --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AjaniNacatlPariah.java @@ -0,0 +1,61 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.Pronoun; +import mage.abilities.common.DiesOneOrMoreCreatureTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.ExileAndReturnSourceEffect; +import mage.abilities.keyword.TransformAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.game.permanent.token.CatWarrior21Token; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class AjaniNacatlPariah extends CardImpl { + + public static final FilterCreaturePermanent filter = new FilterCreaturePermanent(SubType.CAT, "other Cats you control"); + + static { + filter.add(AnotherPredicate.instance); + filter.add(TargetController.YOU.getControllerPredicate()); + } + + public AjaniNacatlPariah(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.CAT); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(1); + this.toughness = new MageInt(2); + + this.secondSideCardClazz = mage.cards.a.AjaniNacatlAvenger.class; + + // When Ajani, Nacatl Pariah enters the battlefield, create a 2/1 white Cat Warrior creature token. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new CatWarrior21Token()))); + + // Whenever one or more other Cats you control die, you may exile Ajani, then return him to the battlefield transformed under his owner's control. + this.addAbility(new TransformAbility()); + this.addAbility(new DiesOneOrMoreCreatureTriggeredAbility( + new ExileAndReturnSourceEffect(PutCards.BATTLEFIELD_TRANSFORMED, Pronoun.HE), + filter + )); + } + + private AjaniNacatlPariah(final AjaniNacatlPariah card) { + super(card); + } + + @Override + public AjaniNacatlPariah copy() { + return new AjaniNacatlPariah(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AkulTheUnrepentant.java b/Mage.Sets/src/mage/cards/a/AkulTheUnrepentant.java new file mode 100644 index 00000000000..a35a36b30f6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AkulTheUnrepentant.java @@ -0,0 +1,62 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.common.LimitedTimesPerTurnActivatedAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.effects.common.PutCardFromHandOntoBattlefieldEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AkulTheUnrepentant extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledCreaturePermanent("other creatures"); + + static { + filter.add(AnotherPredicate.instance); + } + + public AkulTheUnrepentant(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{B}{B}{R}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SCORPION); + this.subtype.add(SubType.DRAGON); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Sacrifice three other creatures: You may put a creature card from your hand onto the battlefield. Activate only as a sorcery and only once each turn. + this.addAbility(new LimitedTimesPerTurnActivatedAbility( + Zone.BATTLEFIELD, + new PutCardFromHandOntoBattlefieldEffect(StaticFilters.FILTER_CARD_CREATURE_A), + new SacrificeTargetCost(3, filter) + ).setTiming(TimingRule.SORCERY)); + } + + private AkulTheUnrepentant(final AkulTheUnrepentant card) { + super(card); + } + + @Override + public AkulTheUnrepentant copy() { + return new AkulTheUnrepentant(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AlelaCunningConqueror.java b/Mage.Sets/src/mage/cards/a/AlelaCunningConqueror.java index 1095469bd99..e98dd15413b 100644 --- a/Mage.Sets/src/mage/cards/a/AlelaCunningConqueror.java +++ b/Mage.Sets/src/mage/cards/a/AlelaCunningConqueror.java @@ -81,7 +81,7 @@ class AlelaCunningConquerorTriggeredAbility extends DealCombatDamageControlledTr if (!super.checkTrigger(event, game)) { return false; } - Player opponent = game.getPlayer(event.getPlayerId()); + Player opponent = game.getPlayer(event.getTargetId()); if (opponent == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/a/AloeAlchemist.java b/Mage.Sets/src/mage/cards/a/AloeAlchemist.java new file mode 100644 index 00000000000..a4b530037e1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AloeAlchemist.java @@ -0,0 +1,59 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BecomesPlottedSourceTriggeredAbility; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.PlotAbility; +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.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class AloeAlchemist extends CardImpl { + + public AloeAlchemist(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); + + this.subtype.add(SubType.PLANT); + this.subtype.add(SubType.WARLOCK); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // When Aloe Alchemist becomes plotted, target creature gets +3/+2 and gains trample until end of turn. + Ability ability = new BecomesPlottedSourceTriggeredAbility( + new BoostTargetEffect(3, 2, Duration.EndOfTurn) + .setText("target creature gets +3/+2") + ); + ability.addTarget(new TargetCreaturePermanent()); + ability.addEffect( + new GainAbilityTargetEffect(TrampleAbility.getInstance(), Duration.EndOfTurn) + .setText("and gains trample until end of turn") + ); + this.addAbility(ability); + + // Plot {1}{G} + this.addAbility(new PlotAbility("{1}{G}")); + } + + private AloeAlchemist(final AloeAlchemist card) { + super(card); + } + + @Override + public AloeAlchemist copy() { + return new AloeAlchemist(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AmbushGigapede.java b/Mage.Sets/src/mage/cards/a/AmbushGigapede.java new file mode 100644 index 00000000000..2e2f59479e6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AmbushGigapede.java @@ -0,0 +1,45 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.keyword.FlashAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetOpponentsCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AmbushGigapede extends CardImpl { + + public AmbushGigapede(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{B}{B}"); + + this.subtype.add(SubType.INSECT); + this.power = new MageInt(6); + this.toughness = new MageInt(2); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // When Ambush Gigapede enters the battlefield, target creature an opponent controls gets -2/-2 until end of turn. + Ability ability = new EntersBattlefieldTriggeredAbility(new BoostTargetEffect(-2, -2)); + ability.addTarget(new TargetOpponentsCreaturePermanent()); + this.addAbility(ability); + } + + private AmbushGigapede(final AmbushGigapede card) { + super(card); + } + + @Override + public AmbushGigapede copy() { + return new AmbushGigapede(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AmyPond.java b/Mage.Sets/src/mage/cards/a/AmyPond.java new file mode 100644 index 00000000000..c9313e55bdd --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AmyPond.java @@ -0,0 +1,97 @@ +package mage.cards.a; + +import java.util.UUID; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.SavedDamageValue; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.PartnerWithAbility; +import mage.cards.*; +import mage.constants.*; +import mage.abilities.keyword.DoctorsCompanionAbility; +import mage.counters.CounterType; +import mage.filter.common.FilterSuspendedCard; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCardInExile; + +/** + * + * @author Skiwkr + */ +public final class AmyPond extends CardImpl { + + private static final FilterSuspendedCard filter = new FilterSuspendedCard("suspended card you own"); + static { + filter.add(TargetController.YOU.getOwnerPredicate()); + } + + public AmyPond(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); + + // Partner with Rory Williams + this.addAbility(new PartnerWithAbility("Rory Williams")); + + // Whenever Amy Pond deals combat damage to a player, choose a suspended card you own and remove that many time counters from it. + Ability ability = new DealsCombatDamageToAPlayerTriggeredAbility(new AmyPondEffect(SavedDamageValue.MANY), + false, true); + ability.addTarget(new TargetCardInExile(filter).withNotTarget(true)); + this.addAbility(ability); + + // Doctor's companion + this.addAbility(DoctorsCompanionAbility.getInstance()); + + } + + private AmyPond(final AmyPond card) { + super(card); + } + + @Override + public AmyPond copy() { + return new AmyPond(this); + } +} + +class AmyPondEffect extends OneShotEffect { + + private final DynamicValue numberCounters; + + AmyPondEffect(DynamicValue numberCounters) { + super(Outcome.Benefit); + this.numberCounters = numberCounters; + this.staticText= "choose a suspended card you own and remove that many time counters from it"; + } + + private AmyPondEffect(final AmyPondEffect effect) { + super(effect); + this.numberCounters = effect.numberCounters; + } + + @Override + public AmyPondEffect copy() { + return new AmyPondEffect(this); + } + + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + Card card = game.getExile().getCard(source.getFirstTarget(), game); + if (card != null) { + card.removeCounters(CounterType.TIME.toString(), (Integer) getValue("damage"), source, game); + return true; + } + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/a/AncientCornucopia.java b/Mage.Sets/src/mage/cards/a/AncientCornucopia.java new file mode 100644 index 00000000000..b985ff4b5e6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AncientCornucopia.java @@ -0,0 +1,83 @@ +package mage.cards.a; + +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.mana.AnyColorManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.FilterSpell; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.ColorlessPredicate; +import mage.game.Game; +import mage.game.stack.Spell; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AncientCornucopia extends CardImpl { + + private static final FilterSpell filter = new FilterSpell("a spell that's one or more colors"); + + static { + filter.add(Predicates.not(ColorlessPredicate.instance)); + } + + public AncientCornucopia(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{G}"); + + // Whenever you cast a spell that's one or more colors, you may gain 1 life for each of that spell's colors. Do this only once each turn. + this.addAbility(new SpellCastControllerTriggeredAbility( + new GainLifeEffect(AncientCornucopiaValue.instance) + .setText("gain 1 life for each of that spell's colors"), filter, true + ).setDoOnlyOnceEachTurn(true)); + + // {T}: Add one mana of any color. + this.addAbility(new AnyColorManaAbility()); + } + + private AncientCornucopia(final AncientCornucopia card) { + super(card); + } + + @Override + public AncientCornucopia copy() { + return new AncientCornucopia(this); + } +} + +enum AncientCornucopiaValue implements DynamicValue { + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return Optional + .ofNullable(effect.getValue("spellCast")) + .map(Spell.class::cast) + .map(spell -> spell.getColor(game)) + .map(ObjectColor::getColorCount) + .orElse(0); + } + + @Override + public AncientCornucopiaValue copy() { + return this; + } + + @Override + public String getMessage() { + return "of that spell's colors"; + } + + @Override + public String toString() { + return "1"; + } +} diff --git a/Mage.Sets/src/mage/cards/a/AngelOfIndemnity.java b/Mage.Sets/src/mage/cards/a/AngelOfIndemnity.java new file mode 100644 index 00000000000..458e2015978 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AngelOfIndemnity.java @@ -0,0 +1,66 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.abilities.keyword.EncoreAbility; +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.ComparisonType; +import mage.constants.SubType; +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 AngelOfIndemnity extends CardImpl { + + private static final FilterCard filter + = new FilterPermanentCard("permanent card with mana value 4 or less from your graveyard"); + + static { + filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, 5)); + } + + public AngelOfIndemnity(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{W}"); + + this.subtype.add(SubType.ANGEL); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // When Angel of Indemnity enters the battlefield, return target permanent card with mana value 4 or less from your graveyard to the battlefield. + Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnFromGraveyardToBattlefieldTargetEffect()); + ability.addTarget(new TargetCardInYourGraveyard(filter)); + this.addAbility(ability); + + // Encore {6}{W}{W} + this.addAbility(new EncoreAbility(new ManaCostsImpl<>("{6}{W}{W}"))); + } + + private AngelOfIndemnity(final AngelOfIndemnity card) { + super(card); + } + + @Override + public AngelOfIndemnity copy() { + return new AngelOfIndemnity(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AngelheartVial.java b/Mage.Sets/src/mage/cards/a/AngelheartVial.java index 9e5a8c4388c..9c8968ee7ff 100644 --- a/Mage.Sets/src/mage/cards/a/AngelheartVial.java +++ b/Mage.Sets/src/mage/cards/a/AngelheartVial.java @@ -68,7 +68,7 @@ class AngelheartVialTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PLAYER; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PLAYER; } @Override diff --git a/Mage.Sets/src/mage/cards/a/AngelicSellSword.java b/Mage.Sets/src/mage/cards/a/AngelicSellSword.java new file mode 100644 index 00000000000..7efc345e814 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AngelicSellSword.java @@ -0,0 +1,79 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.EntersBattlefieldThisOrAnotherTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.token.MercenaryToken; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AngelicSellSword extends CardImpl { + + public AngelicSellSword(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{W}"); + + this.subtype.add(SubType.ANGEL); + this.subtype.add(SubType.MERCENARY); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Whenever Angelic Sell-Sword or another nontoken creature enters the battlefield under your control, create a 1/1 red Mercenary creature token with "{T}: Target creature you control gets +1/+0 until end of turn. Activate only as a sorcery." + this.addAbility(new EntersBattlefieldThisOrAnotherTriggeredAbility( + new CreateTokenEffect(new MercenaryToken()), + StaticFilters.FILTER_CREATURE_NON_TOKEN, false, true + )); + + // Whenever Angelic Sell-Sword attacks, if its power is 6 or greater, draw a card. + this.addAbility(new ConditionalInterveningIfTriggeredAbility( + new AttacksTriggeredAbility(new DrawCardSourceControllerEffect(1)), + AngelicSellSwordCondition.instance, "Whenever {this} attacks, " + + "if its power is 6 or greater, draw a card." + )); + } + + private AngelicSellSword(final AngelicSellSword card) { + super(card); + } + + @Override + public AngelicSellSword copy() { + return new AngelicSellSword(this); + } +} + +enum AngelicSellSwordCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + return Optional + .ofNullable(source.getSourcePermanentOrLKI(game)) + .map(MageObject::getPower) + .map(MageInt::getValue) + .orElse(0) >= 6; + } +} diff --git a/Mage.Sets/src/mage/cards/a/AnkleBiter.java b/Mage.Sets/src/mage/cards/a/AnkleBiter.java new file mode 100644 index 00000000000..21aff7b9359 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AnkleBiter.java @@ -0,0 +1,36 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.keyword.DeathtouchAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AnkleBiter extends CardImpl { + + public AnkleBiter(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}"); + + this.subtype.add(SubType.SNAKE); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Deathtouch + this.addAbility(DeathtouchAbility.getInstance()); + } + + private AnkleBiter(final AnkleBiter card) { + super(card); + } + + @Override + public AnkleBiter copy() { + return new AnkleBiter(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AnnieFlashTheVeteran.java b/Mage.Sets/src/mage/cards/a/AnnieFlashTheVeteran.java new file mode 100644 index 00000000000..1476ad44af2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AnnieFlashTheVeteran.java @@ -0,0 +1,67 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BecomesTappedSourceTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.common.CastFromEverywhereSourceCondition; +import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; +import mage.abilities.effects.common.ExileTopXMayPlayUntilEffect; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.abilities.keyword.FlashAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.common.FilterPermanentCard; +import mage.filter.predicate.mageobject.ManaValuePredicate; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class AnnieFlashTheVeteran extends CardImpl { + + private static final FilterPermanentCard filter = + new FilterPermanentCard("permanent card with mana value 3 or less from your graveyard"); + + static { + filter.add(new ManaValuePredicate(ComparisonType.OR_LESS, 3)); + } + + public AnnieFlashTheVeteran(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}{G}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(4); + this.toughness = new MageInt(5); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // When Annie Flash, the Veteran enters the battlefield, if you cast it, return target permanent card with mana value 3 or less from your graveyard to the battlefield tapped. + Ability ability = new ConditionalInterveningIfTriggeredAbility( + new EntersBattlefieldTriggeredAbility(new ReturnFromGraveyardToBattlefieldTargetEffect(true)), + CastFromEverywhereSourceCondition.instance, + "When {this} enters the battlefield, if you cast it, " + + "return target permanent card with mana value 3 or less from your graveyard to the battlefield tapped" + ); + ability.addTarget(new TargetCardInYourGraveyard(filter)); + this.addAbility(ability); + + // Whenever Annie Flash becomes tapped, exile the top two cards of your library. You may play those cards this turn. + this.addAbility(new BecomesTappedSourceTriggeredAbility(new ExileTopXMayPlayUntilEffect(2, Duration.EndOfTurn))); + } + + private AnnieFlashTheVeteran(final AnnieFlashTheVeteran card) { + super(card); + } + + @Override + public AnnieFlashTheVeteran copy() { + return new AnnieFlashTheVeteran(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AnnieJoinsUp.java b/Mage.Sets/src/mage/cards/a/AnnieJoinsUp.java new file mode 100644 index 00000000000..32708db3738 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AnnieJoinsUp.java @@ -0,0 +1,96 @@ +package mage.cards.a; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreatureOrPlaneswalkerPermanent; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.NumberOfTriggersEvent; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AnnieJoinsUp extends CardImpl { + + private static final FilterPermanent filter + = new FilterCreatureOrPlaneswalkerPermanent("creature or planeswalker an opponent controls"); + + static { + filter.add(TargetController.OPPONENT.getControllerPredicate()); + } + + public AnnieJoinsUp(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{R}{G}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + + // When Annie Joins Up enters the battlefield, it deals 5 damage to target creature or planeswalker an opponent controls. + Ability ability = new EntersBattlefieldTriggeredAbility(new DamageTargetEffect(5)); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + + // If a triggered ability of a legendary creature you control triggers, that ability triggers an additional time. + this.addAbility(new SimpleStaticAbility(new AnnieJoinsUpEffect())); + } + + private AnnieJoinsUp(final AnnieJoinsUp card) { + super(card); + } + + @Override + public AnnieJoinsUp copy() { + return new AnnieJoinsUp(this); + } +} + +class AnnieJoinsUpEffect extends ReplacementEffectImpl { + + AnnieJoinsUpEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit); + staticText = "if a triggered ability of a legendary creature you control triggers, " + + "that ability triggers an additional time"; + } + + private AnnieJoinsUpEffect(final AnnieJoinsUpEffect effect) { + super(effect); + } + + @Override + public AnnieJoinsUpEffect copy() { + return new AnnieJoinsUpEffect(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; + } + Permanent permanent = game.getPermanent(((NumberOfTriggersEvent) event).getSourceId()); + return permanent != null + && permanent.isControlledBy(source.getControllerId()) + && permanent.isLegendary(game); + } + + @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/a/AnotherRound.java b/Mage.Sets/src/mage/cards/a/AnotherRound.java new file mode 100644 index 00000000000..7bc6ff5d281 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AnotherRound.java @@ -0,0 +1,89 @@ +package mage.cards.a; + +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.dynamicvalue.common.ManacostVariableValue; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileThenReturnTargetEffect; +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.players.Player; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.targetpointer.FixedTargets; + +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * @author Susucr + */ +public final class AnotherRound extends CardImpl { + + public AnotherRound(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{X}{X}{2}{W}"); + + // Exile any number of creatures you control, then return them to the battlefield under their owner's control. Then repeat this process X times. + this.getSpellAbility().addEffect(new AnotherRoundEffect()); + } + + private AnotherRound(final AnotherRound card) { + super(card); + } + + @Override + public AnotherRound copy() { + return new AnotherRound(this); + } +} + +class AnotherRoundEffect extends OneShotEffect { + + public AnotherRoundEffect() { + super(Outcome.Benefit); + staticText = "Exile any number of creatures you control, " + + "then return them to the battlefield under their owner's control. " + + "Then repeat this process X more times."; + } + + private AnotherRoundEffect(final AnotherRoundEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + + int xValue = ManacostVariableValue.REGULAR.calculate(game, source, this); + TargetControlledCreaturePermanent target = + new TargetControlledCreaturePermanent( + 0, Integer.MAX_VALUE, + StaticFilters.FILTER_CONTROLLED_CREATURE, true + ); + + for (int i = 0; i <= xValue; ++i) { + target.clearChosen(); + controller.chooseTarget(Outcome.Benefit, target, source, game); + new ExileThenReturnTargetEffect(false, true) + .setTargetPointer(new FixedTargets( + target.getTargets() + .stream() + .map(id -> new MageObjectReference(id, game)) + .collect(Collectors.toList()) + )).apply(game, source); + game.getState().processAction(game); + } + return true; + } + + @Override + public AnotherRoundEffect copy() { + return new AnotherRoundEffect(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AnzragsRampage.java b/Mage.Sets/src/mage/cards/a/AnzragsRampage.java new file mode 100644 index 00000000000..c3f432f792b --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AnzragsRampage.java @@ -0,0 +1,184 @@ +package mage.cards.a; + +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DestroyAllEffect; +import mage.abilities.effects.common.ReturnToHandTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.hint.ValueHint; +import mage.abilities.keyword.HasteAbility; +import mage.cards.*; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.filter.common.FilterArtifactPermanent; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInExile; +import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; +import mage.watchers.Watcher; + +/** + * @author Cguy7777 + */ +public final class AnzragsRampage extends CardImpl { + + private static final FilterArtifactPermanent filter = new FilterArtifactPermanent("artifacts you don't control"); + + static { + filter.add(TargetController.NOT_YOU.getControllerPredicate()); + } + + private static final ValueHint hint = new ValueHint( + "Artifacts put into graveyards from the battlefield this turn", AnzragsRampageValue.instance); + + public AnzragsRampage(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{R}{R}"); + + // Destroy all artifacts you don't control, then exile the top X cards of your library, where X is the number of artifacts that were put into graveyards from the battlefield this turn. + // You may put a creature card exiled this way onto the battlefield. It gains haste. Return it to your hand at the beginning of the next end step. + this.getSpellAbility().addEffect(new DestroyAllEffect(filter)); + this.getSpellAbility().addEffect(new AnzragsRampageEffect().concatBy(", then")); + this.getSpellAbility().addWatcher(new AnzragsRampageWatcher()); + this.getSpellAbility().addHint(hint); + } + + private AnzragsRampage(final AnzragsRampage card) { + super(card); + } + + @Override + public AnzragsRampage copy() { + return new AnzragsRampage(this); + } +} + +class AnzragsRampageWatcher extends Watcher { + + private int artifactsDied; + + AnzragsRampageWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() == GameEvent.EventType.ZONE_CHANGE) { + ZoneChangeEvent zEvent = (ZoneChangeEvent) event; + if (zEvent.isDiesEvent() && zEvent.getTarget().isArtifact(game)) { + artifactsDied++; + } + } + } + + @Override + public void reset() { + super.reset(); + artifactsDied = 0; + } + + public int getArtifactsDied() { + return artifactsDied; + } +} + +enum AnzragsRampageValue implements DynamicValue { + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + AnzragsRampageWatcher watcher = game.getState().getWatcher(AnzragsRampageWatcher.class); + if (watcher == null) { + return 0; + } + return watcher.getArtifactsDied(); + } + + @Override + public AnzragsRampageValue copy() { + return instance; + } + + @Override + public String getMessage() { + return ""; + } +} + +class AnzragsRampageEffect extends OneShotEffect { + + AnzragsRampageEffect() { + super(Outcome.PutCreatureInPlay); + staticText = "exile the top X cards of your library, where X is the number of artifacts " + + "that were put into graveyards from the battlefield this turn. " + + "You may put a creature card exiled this way onto the battlefield. It gains haste. " + + "Return it to your hand at the beginning of the next end step"; + } + + private AnzragsRampageEffect(final AnzragsRampageEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + + // Exile the top X cards of your library, + // where X is the number of artifacts that were put into graveyards from the battlefield this turn. + Cards cards = new CardsImpl(controller.getLibrary() + .getTopCards(game, AnzragsRampageValue.instance.calculate(game, source, this))); + controller.moveCardsToExile( + cards.getCards(game), source, game, true, + CardUtil.getExileZoneId(game, source), + CardUtil.createObjectRealtedWindowTitle(source, game, null)); + + // You may put a creature card exiled this way onto the battlefield. + TargetCard targetCard = new TargetCardInExile( + 0, 1, StaticFilters.FILTER_CARD_CREATURE, CardUtil.getExileZoneId(game, source)); + targetCard.withNotTarget(true); + controller.choose(outcome, targetCard, source, game); + Card card = game.getCard(targetCard.getFirstTarget()); + if (card == null) { + return true; + } + + if (controller.moveCards(card, Zone.BATTLEFIELD, source, game)) { + Permanent permanent = game.getPermanent(card.getId()); + if (permanent == null) { + return true; + } + + // It gains haste. + ContinuousEffect hasteEffect = new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.Custom); + hasteEffect.setTargetPointer(new FixedTarget(permanent, game)); + game.addEffect(hasteEffect, source); + + // Return it to your hand at the beginning of the next end step. + ReturnToHandTargetEffect returnToHandEffect = new ReturnToHandTargetEffect(); + returnToHandEffect.setText("return it to your hand"); + returnToHandEffect.setTargetPointer(new FixedTarget(permanent, game)); + DelayedTriggeredAbility delayedAbility = new AtTheBeginOfNextEndStepDelayedTriggeredAbility(returnToHandEffect); + game.addDelayedTriggeredAbility(delayedAbility, source); + } + return true; + } + + @Override + public AnzragsRampageEffect copy() { + return new AnzragsRampageEffect(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/ArcadeGannon.java b/Mage.Sets/src/mage/cards/a/ArcadeGannon.java new file mode 100644 index 00000000000..f70ee03061b --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/ArcadeGannon.java @@ -0,0 +1,83 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.CastFromGraveyardOnceEachTurnAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.effects.common.DrawDiscardControllerEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +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.counters.CounterType; +import mage.filter.FilterCard; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.filter.predicate.Predicates; +import mage.game.Game; +import mage.game.permanent.Permanent; + +import java.util.UUID; + +/** + * + * @author justinjohnson14 + */ +public final class ArcadeGannon extends CardImpl { + + private static final FilterCard filter = new FilterCard("an artifact or Human spell from your graveyard with mana value less than or equal to the number of quest counters on {this}"); + + static{ + filter.add(Predicates.or( + CardType.ARTIFACT.getPredicate(), + SubType.HUMAN.getPredicate() + )); + filter.add(ArcadeGannonPredicate.instance); + } + + public ArcadeGannon(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.DOCTOR); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // {T}: Draw a card, then discard a card. Put a quest counter on Arcade Gannon. + Ability ability = (new SimpleActivatedAbility(new DrawDiscardControllerEffect(1,1), new TapSourceCost())); + ability.addEffect(new AddCountersSourceEffect(CounterType.QUEST.createInstance(1))); + this.addAbility(ability); + + // For Auld Lang Syne -- Once during each of your turns, you may cast an artifact or Human spell from your graveyard with mana value less than or equal to the number of quest counters on Arcade Gannon. + this.addAbility(new CastFromGraveyardOnceEachTurnAbility(filter).withFlavorWord("For Auld Lang Syne")); + } + + private ArcadeGannon(final ArcadeGannon card) { + super(card); + } + + @Override + public ArcadeGannon copy() { + return new ArcadeGannon(this); + } +} + +enum ArcadeGannonPredicate implements ObjectSourcePlayerPredicate { + instance; + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + Permanent sourcePermanent = input.getSource().getSourcePermanentOrLKI(game); + return sourcePermanent != null && input.getObject().getManaValue() <= sourcePermanent.getCounters(game).getCount(CounterType.QUEST); + } + + @Override + public String toString() { + return "mana value less than or equal to {this}'s power"; + } +} diff --git a/Mage.Sets/src/mage/cards/a/ArcaneHeist.java b/Mage.Sets/src/mage/cards/a/ArcaneHeist.java new file mode 100644 index 00000000000..e68f24e8fc4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/ArcaneHeist.java @@ -0,0 +1,51 @@ +package mage.cards.a; + +import mage.abilities.effects.common.CipherEffect; +import mage.abilities.effects.common.MayCastTargetCardEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.CastManaAdjustment; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicates; +import mage.target.common.TargetCardInOpponentsGraveyard; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class ArcaneHeist extends CardImpl { + + private static final FilterCard filter = new FilterCard("instant or sorcery card from an opponent's graveyard"); + + static { + filter.add(Predicates.or( + CardType.INSTANT.getPredicate(), + CardType.SORCERY.getPredicate())); + } + + public ArcaneHeist(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{U}{U}"); + + // You may cast target instant or sorcery card from an opponent's graveyard without paying its mana cost. If that spell would be put into their graveyard, exile it instead. + this.getSpellAbility().addEffect( + new MayCastTargetCardEffect(CastManaAdjustment.WITHOUT_PAYING_MANA_COST, true) + .setText("You may cast target instant or sorcery card from an opponent's graveyard without paying its mana cost. " + + "If that spell would be put into their graveyard, exile it instead.") + ); + this.getSpellAbility().addTarget(new TargetCardInOpponentsGraveyard(filter)); + + // Cipher + this.getSpellAbility().addEffect(new CipherEffect()); + } + + private ArcaneHeist(final ArcaneHeist card) { + super(card); + } + + @Override + public ArcaneHeist copy() { + return new ArcaneHeist(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/Arcbond.java b/Mage.Sets/src/mage/cards/a/Arcbond.java index 84f53d47eb3..a7e99622ba0 100644 --- a/Mage.Sets/src/mage/cards/a/Arcbond.java +++ b/Mage.Sets/src/mage/cards/a/Arcbond.java @@ -84,7 +84,7 @@ class ArcbondDelayedTriggeredAbility extends DelayedTriggeredAbility { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT; } @Override diff --git a/Mage.Sets/src/mage/cards/a/ArchmagesNewt.java b/Mage.Sets/src/mage/cards/a/ArchmagesNewt.java new file mode 100644 index 00000000000..43d294c2e73 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/ArchmagesNewt.java @@ -0,0 +1,96 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.condition.common.SaddledCondition; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.keyword.FlashbackAbility; +import mage.abilities.keyword.SaddleAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ArchmagesNewt extends CardImpl { + + public ArchmagesNewt(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}"); + + this.subtype.add(SubType.SALAMANDER); + this.subtype.add(SubType.MOUNT); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Whenever Archmage's Newt deals combat damage to a player, target instant or sorcery card in your graveyard gains flashback until end of turn. The flashback cost is equal to its mana cost. That card gains flashback {0} until end of turn instead if Archmage's Newt is saddled. + Ability ability = new DealsCombatDamageToAPlayerTriggeredAbility(new ArchmagesNewtEffect(), false); + ability.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY_FROM_YOUR_GRAVEYARD)); + this.addAbility(ability); + + // Saddle 3 + this.addAbility(new SaddleAbility(3)); + } + + private ArchmagesNewt(final ArchmagesNewt card) { + super(card); + } + + @Override + public ArchmagesNewt copy() { + return new ArchmagesNewt(this); + } +} + +class ArchmagesNewtEffect extends ContinuousEffectImpl { + + private boolean saddled = false; + + ArchmagesNewtEffect() { + super(Duration.EndOfTurn, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); + this.staticText = "target instant or sorcery card in your graveyard gains flashback until end of turn. " + + "The flashback cost is equal to its mana cost. That card gains flashback {0} until end of turn instead if {this} is saddled"; + } + + private ArchmagesNewtEffect(final ArchmagesNewtEffect effect) { + super(effect); + this.saddled = effect.saddled; + } + + @Override + public ArchmagesNewtEffect copy() { + return new ArchmagesNewtEffect(this); + } + + @Override + public void init(Ability source, Game game) { + super.init(source, game); + this.saddled = SaddledCondition.instance.apply(game, source); + } + + @Override + public boolean apply(Game game, Ability source) { + Card card = game.getCard(getTargetPointer().getFirst(game, source)); + if (card == null) { + return false; + } + FlashbackAbility ability; + if (saddled) { + ability = new FlashbackAbility(card, new GenericManaCost(0)); + } else { + ability = new FlashbackAbility(card, card.getManaCost()); + } + ability.setSourceId(card.getId()); + ability.setControllerId(card.getOwnerId()); + game.getState().addOtherAbility(card, ability); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/a/ArcoFlagellant.java b/Mage.Sets/src/mage/cards/a/ArcoFlagellant.java index 895477fbe2a..924f286b761 100644 --- a/Mage.Sets/src/mage/cards/a/ArcoFlagellant.java +++ b/Mage.Sets/src/mage/cards/a/ArcoFlagellant.java @@ -4,6 +4,7 @@ import mage.MageInt; import mage.abilities.common.CantBlockAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.PayLifeCost; +import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; import mage.abilities.keyword.IndestructibleAbility; import mage.abilities.keyword.SquadAbility; @@ -28,7 +29,7 @@ public final class ArcoFlagellant extends CardImpl { this.toughness = new MageInt(1); // Squad {2} - this.addAbility(new SquadAbility()); + this.addAbility(new SquadAbility(new GenericManaCost(2))); // Arco-Flagellant can't block. this.addAbility(new CantBlockAbility()); diff --git a/Mage.Sets/src/mage/cards/a/AridArchway.java b/Mage.Sets/src/mage/cards/a/AridArchway.java new file mode 100644 index 00000000000..5a354f595b9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AridArchway.java @@ -0,0 +1,96 @@ +package mage.cards.a; + +import mage.MageObjectReference; +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTappedAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.mana.SimpleManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.StaticFilters; +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 AridArchway extends CardImpl { + + public AridArchway(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + this.subtype.add(SubType.DESERT); + + // Arid Archway enters the battlefield tapped. + this.addAbility(new EntersBattlefieldTappedAbility()); + + // When Arid Archway enters the battlefield, return a land you control to its owner's hand. If another Desert was returned this way, surveil 1. + this.addAbility(new EntersBattlefieldTriggeredAbility(new AridArchwayEffect())); + + // {T}: Add {C}{C}. + this.addAbility(new SimpleManaAbility(Zone.BATTLEFIELD, Mana.ColorlessMana(2), new TapSourceCost())); + } + + private AridArchway(final AridArchway card) { + super(card); + } + + @Override + public AridArchway copy() { + return new AridArchway(this); + } +} + +class AridArchwayEffect extends OneShotEffect { + + AridArchwayEffect() { + super(Outcome.Benefit); + staticText = "return a land you control to its owner's hand. If another Desert was returned this way, surveil 1"; + } + + private AridArchwayEffect(final AridArchwayEffect effect) { + super(effect); + } + + @Override + public AridArchwayEffect copy() { + return new AridArchwayEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + if (!game.getBattlefield().contains(StaticFilters.FILTER_CONTROLLED_PERMANENT_LAND, source, game, 1)) { + return false; + } + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + TargetPermanent target = new TargetPermanent(StaticFilters.FILTER_CONTROLLED_PERMANENT_LAND); + target.withNotTarget(true); + player.choose(outcome, target, source, game); + Permanent permanent = game.getPermanent(target.getFirstTarget()); + if (permanent == null) { + return false; + } + boolean flag = permanent.hasSubtype(SubType.DESERT, game) + && !new MageObjectReference(permanent, game) + .refersTo(source, game); + player.moveCards(permanent, Zone.HAND, source, game); + if (flag) { + player.surveil(1, source, game); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/a/ArmoredArmadillo.java b/Mage.Sets/src/mage/cards/a/ArmoredArmadillo.java new file mode 100644 index 00000000000..535ad5a0f9e --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/ArmoredArmadillo.java @@ -0,0 +1,49 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.common.SourcePermanentToughnessValue; +import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.keyword.WardAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ArmoredArmadillo extends CardImpl { + + public ArmoredArmadillo(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W}"); + + this.subtype.add(SubType.ARMADILLO); + this.power = new MageInt(0); + this.toughness = new MageInt(4); + + // Ward {1} + this.addAbility(new WardAbility(new ManaCostsImpl<>("{1}"))); + + // {3}{W}: Armored Armadillo gets +X/+0 until end of turn, where X is its toughness. + this.addAbility(new SimpleActivatedAbility( + new BoostSourceEffect( + SourcePermanentToughnessValue.getInstance(), StaticValue.get(0), Duration.EndOfTurn + ).setText("{this} gets +X/+0 until end of turn, where X is its toughness."), + new ManaCostsImpl<>("{3}{W}"))); + } + + private ArmoredArmadillo(final ArmoredArmadillo card) { + super(card); + } + + @Override + public ArmoredArmadillo copy() { + return new ArmoredArmadillo(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AsLuckWouldHaveIt.java b/Mage.Sets/src/mage/cards/a/AsLuckWouldHaveIt.java index f41a1d8025e..d15903fddb1 100644 --- a/Mage.Sets/src/mage/cards/a/AsLuckWouldHaveIt.java +++ b/Mage.Sets/src/mage/cards/a/AsLuckWouldHaveIt.java @@ -74,7 +74,7 @@ class AsLuckWouldHaveItTriggeredAbility extends TriggeredAbilityImpl { // Any die roll with a numerical result will add luck counters to As Luck Would Have It. // Rolling the planar die will not cause the second ability to trigger. // (2018-01-19) - if (this.isControlledBy(event.getPlayerId()) && drEvent.getRollDieType() == RollDieType.NUMERICAL) { + if (this.isControlledBy(event.getTargetId()) && drEvent.getRollDieType() == RollDieType.NUMERICAL) { // silver border card must look for "result" instead "natural result" this.getEffects().setValue("rolled", drEvent.getResult()); return true; diff --git a/Mage.Sets/src/mage/cards/a/AssembleThePlayers.java b/Mage.Sets/src/mage/cards/a/AssembleThePlayers.java index 18b2dddf2d3..a52007b7c0f 100644 --- a/Mage.Sets/src/mage/cards/a/AssembleThePlayers.java +++ b/Mage.Sets/src/mage/cards/a/AssembleThePlayers.java @@ -3,21 +3,23 @@ package mage.cards.a; import mage.MageIdentifier; import mage.MageObjectReference; import mage.abilities.Ability; +import mage.abilities.SpellAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.AsThoughEffectImpl; import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; -import mage.abilities.hint.Hint; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.AsThoughEffectType; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; import mage.game.Game; -import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.players.Player; -import mage.watchers.Watcher; +import mage.watchers.common.OnceEachTurnCastWatcher; -import java.util.*; +import java.util.UUID; /** * @author xenohedron @@ -33,9 +35,9 @@ public final class AssembleThePlayers extends CardImpl { // Once each turn, you may cast a creature spell with power 2 or less from the top of your library. this.addAbility( new SimpleStaticAbility(new AssembleThePlayersPlayTopEffect()) - .setIdentifier(MageIdentifier.AssembleThePlayersWatcher) - .addHint(AssembleThePlayersHint.instance), - new AssembleThePlayersWatcher() + .setIdentifier(MageIdentifier.OnceEachTurnCastWatcher) + .addHint(OnceEachTurnCastWatcher.getHint()), + new OnceEachTurnCastWatcher() // all based on Johann, Apprentice Sorcerer ); @@ -51,30 +53,6 @@ public final class AssembleThePlayers extends CardImpl { } } -enum AssembleThePlayersHint implements Hint { - instance; - - @Override - public String getText(Game game, Ability ability) { - AssembleThePlayersWatcher watcher = game.getState().getWatcher(AssembleThePlayersWatcher.class); - if (watcher != null) { - boolean used = watcher.isAbilityUsed(ability.getControllerId(), new MageObjectReference(ability.getSourceId(), game)); - if (used) { - Player player = game.getPlayer(ability.getControllerId()); - if (player != null) { - return "A spell has been cast by " + player.getLogName() + " with {this} this turn."; - } - } - } - return ""; - } - - @Override - public AssembleThePlayersHint copy() { - return this; - } -} - class AssembleThePlayersPlayTopEffect extends AsThoughEffectImpl { AssembleThePlayersPlayTopEffect() { @@ -98,62 +76,41 @@ class AssembleThePlayersPlayTopEffect extends AsThoughEffectImpl { @Override public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { - // Only applies for the controller of the ability. - if (!affectedControllerId.equals(source.getControllerId())) { - return false; - } + throw new IllegalArgumentException("Wrong code usage: can't call applies method on empty affectedAbility"); + } + @Override + public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) { Player controller = game.getPlayer(source.getControllerId()); - AssembleThePlayersWatcher watcher = game.getState().getWatcher(AssembleThePlayersWatcher.class); - Permanent sourceObject = game.getPermanent(source.getSourceId()); - if (controller == null || watcher == null || sourceObject == null) { + OnceEachTurnCastWatcher watcher = game.getState().getWatcher(OnceEachTurnCastWatcher.class); + Permanent sourcePermanent = source.getSourcePermanentIfItStillExists(game); + if (controller == null || sourcePermanent == null || watcher == null) { + return false; + } + // Only applies for the controller of the ability. + if (!playerId.equals(source.getControllerId())) { return false; } - // Has the ability already been used this turn by the player? - if (watcher.isAbilityUsed(controller.getId(), new MageObjectReference(sourceObject, game))) { + if (watcher.isAbilityUsed(controller.getId(), new MageObjectReference(sourcePermanent, game))) { return false; } - Card card = game.getCard(objectId); Card topCard = controller.getLibrary().getFromTop(game); // Is the card attempted to be played the top card of the library? if (card == null || topCard == null || !topCard.getId().equals(card.getMainCard().getId())) { return false; } - - // Only works for creatures with power 2 or less - return card.isCreature(game) && card.getPower().getValue() <=2; - } -} - -class AssembleThePlayersWatcher extends Watcher { - - // player -> set of all permanent's mor that already used their once per turn Approval. - private final Map> usedFrom = new HashMap<>(); - - public AssembleThePlayersWatcher() { - super(WatcherScope.GAME); - } - - @Override - public void watch(GameEvent event, Game game) { - UUID playerId = event.getPlayerId(); - if (event.getType() == GameEvent.EventType.SPELL_CAST - && event.hasApprovingIdentifier(MageIdentifier.AssembleThePlayersWatcher) - && playerId != null) { - usedFrom.computeIfAbsent(playerId, k -> new HashSet<>()) - .add(event.getAdditionalReference().getApprovingMageObjectReference()); + if (affectedAbility instanceof SpellAbility) { + SpellAbility spellAbility = (SpellAbility) affectedAbility; + if (spellAbility.getManaCosts().isEmpty() + || !spellAbility.spellCanBeActivatedRegularlyNow(playerId, game)) { + return false; + } + Card cardToCheck = spellAbility.getCharacteristics(game); + // Only works for creatures with power 2 or less + return cardToCheck.isCreature(game) && cardToCheck.getPower().getValue() <= 2; } - } - - @Override - public void reset() { - super.reset(); - usedFrom.clear(); - } - - public boolean isAbilityUsed(UUID playerId, MageObjectReference mor) { - return usedFrom.getOrDefault(playerId, Collections.emptySet()).contains(mor); + return false; } } diff --git a/Mage.Sets/src/mage/cards/a/AssimilationAegis.java b/Mage.Sets/src/mage/cards/a/AssimilationAegis.java new file mode 100644 index 00000000000..1048d496b39 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AssimilationAegis.java @@ -0,0 +1,134 @@ +package mage.cards.a; + +import mage.abilities.Ability; +import mage.abilities.common.AttachedToCreatureSourceTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CopyEffect; +import mage.abilities.effects.common.ExileUntilSourceLeavesEffect; +import mage.abilities.keyword.EquipAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInExile; +import mage.target.common.TargetCreaturePermanent; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class AssimilationAegis extends CardImpl { + + public AssimilationAegis(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{W}{U}"); + + this.subtype.add(SubType.EQUIPMENT); + + // When Assimilation Aegis enters the battlefield, exile up to one target creature until Assimilation Aegis leaves the battlefield. + Ability ability = new EntersBattlefieldTriggeredAbility(new ExileUntilSourceLeavesEffect()); + ability.addTarget(new TargetCreaturePermanent(0, 1)); + this.addAbility(ability); + + // Whenever Assimilation Aegis becomes attached to a creature, for as long as Assimilation Aegis remains attached to it, that creature becomes a copy of a creature card exiled with Assimilation Aegis. + this.addAbility(new AttachedToCreatureSourceTriggeredAbility(new AssimilationAegisEffect(), false)); + + // Equip {2} + this.addAbility(new EquipAbility(Outcome.BoostCreature, new GenericManaCost(2))); + } + + private AssimilationAegis(final AssimilationAegis card) { + super(card); + } + + @Override + public AssimilationAegis copy() { + return new AssimilationAegis(this); + } +} + +// Similar to Blade of Shared Souls. +class AssimilationAegisEffect extends OneShotEffect { + + AssimilationAegisEffect() { + super(Outcome.Benefit); + staticText = "for as long as {this} remains attached to it, " + + "that creature becomes a copy of a creature card exiled with {this}"; + } + + private AssimilationAegisEffect(final AssimilationAegisEffect effect) { + super(effect); + } + + @Override + public AssimilationAegisEffect copy() { + return new AssimilationAegisEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent attachedPermanent = (Permanent) getValue("attachedPermanent"); + Permanent equipment = source.getSourcePermanentIfItStillExists(game); + if (attachedPermanent == null + || equipment == null + || !equipment.isAttachedTo(attachedPermanent.getId())) { + return false; + } + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + UUID exileId = CardUtil.getExileZoneId(game, source); + TargetCard target = new TargetCardInExile(StaticFilters.FILTER_CARD_CREATURE, exileId); + target.withNotTarget(true); + if (!target.choose(Outcome.Benefit, player.getId(), source.getId(), source, game)) { + return false; + } + Card copyCard = game.getCard(target.getFirstTarget()); + if (copyCard == null) { + return false; + } + game.addEffect(new AssimilationAegisCopyEffect(copyCard, attachedPermanent), source); + return true; + } +} + +// Similar to Blade of Shared Souls. +class AssimilationAegisCopyEffect extends CopyEffect { + + AssimilationAegisCopyEffect(Card copyCard, Permanent attachedPermanent) { + super(Duration.Custom, copyCard, attachedPermanent.getId()); + } + + private AssimilationAegisCopyEffect(final AssimilationAegisCopyEffect effect) { + super(effect); + } + + @Override + public AssimilationAegisCopyEffect copy() { + return new AssimilationAegisCopyEffect(this); + } + + @Override + public boolean isInactive(Ability source, Game game) { + if (super.isInactive(source, game)) { + return true; + } + Permanent sourcePermanent = source.getSourcePermanentIfItStillExists(game); + if (sourcePermanent == null || !sourcePermanent.isAttachedTo(this.copyToObjectId)) { + return true; + } + return false; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/a/AtKnifepoint.java b/Mage.Sets/src/mage/cards/a/AtKnifepoint.java new file mode 100644 index 00000000000..1ec619808f9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AtKnifepoint.java @@ -0,0 +1,52 @@ +package mage.cards.a; + +import mage.abilities.common.CommittedCrimeTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.MyTurnCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.filter.FilterPermanent; +import mage.filter.predicate.mageobject.OutlawPredicate; +import mage.game.permanent.token.MercenaryToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AtKnifepoint extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent(); + + static { + filter.add(OutlawPredicate.instance); + } + + public AtKnifepoint(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{B}{R}"); + + // As long as it's your turn, outlaws you control have first strike. + this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( + new GainAbilityControlledEffect(FirstStrikeAbility.getInstance(), Duration.WhileOnBattlefield, filter), + MyTurnCondition.instance, "as long as it's your turn, outlaws you control have first strike" + ))); + + // Whenever you commit a crime, create a 1/1 red Mercenary creature token with "{T}: Target creature you control gets +1/+0 until end of turn. Activate only as a sorcery." This ability triggers only once each turn. + this.addAbility(new CommittedCrimeTriggeredAbility(new CreateTokenEffect(new MercenaryToken())).setTriggersOnceEachTurn(true)); + } + + private AtKnifepoint(final AtKnifepoint card) { + super(card); + } + + @Override + public AtKnifepoint copy() { + return new AtKnifepoint(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AugurOfAutumn.java b/Mage.Sets/src/mage/cards/a/AugurOfAutumn.java index 26e26f2d42b..ce363acd115 100644 --- a/Mage.Sets/src/mage/cards/a/AugurOfAutumn.java +++ b/Mage.Sets/src/mage/cards/a/AugurOfAutumn.java @@ -8,7 +8,7 @@ import mage.abilities.condition.common.CovenCondition; import mage.abilities.decorator.ConditionalAsThoughEffect; import mage.abilities.effects.Effect; import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; -import mage.abilities.effects.common.continuous.PlayTheTopCardEffect; +import mage.abilities.effects.common.continuous.PlayFromTopOfLibraryEffect; import mage.abilities.hint.common.CovenHint; import mage.constants.AbilityWord; import mage.constants.SubType; @@ -41,11 +41,11 @@ public final class AugurOfAutumn extends CardImpl { this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); // You may play lands from the top of your library. - this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(TargetController.YOU, filter, false))); + this.addAbility(new SimpleStaticAbility(new PlayFromTopOfLibraryEffect(filter))); // Coven — As long as you control three or more creatures with different powers, you may cast creature spells from the top of your library. Effect effect = new ConditionalAsThoughEffect( - new PlayTheTopCardEffect(TargetController.YOU, filter2, false), + new PlayFromTopOfLibraryEffect(filter2), CovenCondition.instance ); effect.setText("As long as you control three or more creatures with different powers, you may cast creature spells from the top of your library"); diff --git a/Mage.Sets/src/mage/cards/a/AureliasVindicator.java b/Mage.Sets/src/mage/cards/a/AureliasVindicator.java new file mode 100644 index 00000000000..a88490554c4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AureliasVindicator.java @@ -0,0 +1,85 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.LeavesBattlefieldTriggeredAbility; +import mage.abilities.common.TurnedFaceUpSourceTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.common.MorphManacostVariableValue; +import mage.abilities.effects.common.ExileTargetForSourceEffect; +import mage.abilities.effects.common.ReturnFromExileForSourceEffect; +import mage.abilities.keyword.DisguiseAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.LifelinkAbility; +import mage.abilities.keyword.WardAbility; +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.game.Game; +import mage.target.Target; +import mage.target.common.TargetCardInGraveyardBattlefieldOrStack; +import mage.target.targetadjustment.TargetAdjuster; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class AureliasVindicator extends CardImpl { + + public AureliasVindicator(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}{W}"); + + this.subtype.add(SubType.ANGEL); + this.power = new MageInt(4); + this.toughness = new MageInt(2); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // Ward {2} + this.addAbility(new WardAbility(new ManaCostsImpl<>("{2}"))); + + // Disguise {X}{3}{W} + this.addAbility(new DisguiseAbility(this, new ManaCostsImpl<>("{X}{3}{W}"))); + + // When Aurelia's Vindicator is turned face up, exile up to X other target creatures from the battlefield and/or creature cards from graveyards. + Ability ability = new TurnedFaceUpSourceTriggeredAbility(new ExileTargetForSourceEffect() + .setText("exile up to X other target creatures from the battlefield and/or creature cards from graveyards")); + ability.setTargetAdjuster(AureliasVindicatorAdjuster.instance); + this.addAbility(ability); + + // When Aurelia's Vindicator leaves the battlefield, return the exiled cards to their owners' hands. + this.addAbility(new LeavesBattlefieldTriggeredAbility(new ReturnFromExileForSourceEffect(Zone.HAND) + .withText(true, true, false), false)); + } + + private AureliasVindicator(final AureliasVindicator card) { + super(card); + } + + @Override + public AureliasVindicator copy() { + return new AureliasVindicator(this); + } +} + +enum AureliasVindicatorAdjuster implements TargetAdjuster { + instance; + + @Override + public void adjustTargets(Ability ability, Game game) { + ability.getTargets().clear(); + int xValue = MorphManacostVariableValue.instance.calculate(game, ability, null); + Target target = new TargetCardInGraveyardBattlefieldOrStack( + 0, xValue, StaticFilters.FILTER_CARD_CREATURE, StaticFilters.FILTER_PERMANENT_CREATURES + ); + ability.addTarget(target); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AvenInterrupter.java b/Mage.Sets/src/mage/cards/a/AvenInterrupter.java new file mode 100644 index 00000000000..95cfe5a1ed4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AvenInterrupter.java @@ -0,0 +1,104 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.cost.SpellsCostIncreasingAllEffect; +import mage.abilities.keyword.FlashAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.PlotAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.card.CastFromZonePredicate; +import mage.game.Game; +import mage.game.stack.Spell; +import mage.players.Player; +import mage.target.TargetSpell; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class AvenInterrupter extends CardImpl { + + private static final FilterCard filter = new FilterCard("spells your opponents cast from graveyards or from exile"); + + static { + filter.add(Predicates.or( + new CastFromZonePredicate(Zone.GRAVEYARD), + new CastFromZonePredicate(Zone.EXILED) + )); + } + + public AvenInterrupter(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}{W}"); + + this.subtype.add(SubType.BIRD); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When Aven Interrupter enters the battlefield, exile target spell. It becomes plotted. + Ability ability = new EntersBattlefieldTriggeredAbility(new AvenInterrupterEffect()); + ability.addTarget(new TargetSpell()); + this.addAbility(ability); + + // Spells your opponents cast from graveyards or from exile cost 2 more to cast. + this.addAbility(new SimpleStaticAbility( + new SpellsCostIncreasingAllEffect(2, filter, TargetController.OPPONENT) + )); + } + + private AvenInterrupter(final AvenInterrupter card) { + super(card); + } + + @Override + public AvenInterrupter copy() { + return new AvenInterrupter(this); + } +} + +class AvenInterrupterEffect extends OneShotEffect { + + AvenInterrupterEffect() { + super(Outcome.Detriment); + staticText = "exile target spell. It becomes plotted"; + } + + private AvenInterrupterEffect(final AvenInterrupterEffect effect) { + super(effect); + } + + @Override + public AvenInterrupterEffect copy() { + return new AvenInterrupterEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + MageObject sourceObject = source.getSourceObject(game); + if (controller == null || sourceObject == null) { + return false; + } + Spell spell = game.getStack().getSpell(getTargetPointer().getFirst(game, source)); + if (spell == null) { + return false; + } + return PlotAbility.doExileAndPlotCard(spell, game, source); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/b/BackInTown.java b/Mage.Sets/src/mage/cards/b/BackInTown.java new file mode 100644 index 00000000000..7a055e4ae04 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BackInTown.java @@ -0,0 +1,53 @@ +package mage.cards.b; + +import mage.abilities.Ability; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.FilterCard; +import mage.filter.predicate.mageobject.OutlawPredicate; +import mage.game.Game; +import mage.target.common.TargetCardInYourGraveyard; +import mage.target.targetadjustment.TargetAdjuster; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BackInTown extends CardImpl { + + public BackInTown(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{X}{2}{B}"); + + // Return X target outlaw creature cards from your graveyard to the battlefield. + this.getSpellAbility().addEffect(new ReturnFromGraveyardToBattlefieldTargetEffect() + .setText("return X target outlaw creature cards from your graveyard to the battlefield")); + this.getSpellAbility().setTargetAdjuster(BackInTownAdjuster.instance); + } + + private BackInTown(final BackInTown card) { + super(card); + } + + @Override + public BackInTown copy() { + return new BackInTown(this); + } +} + +enum BackInTownAdjuster implements TargetAdjuster { + instance; + private static final FilterCard filter = new FilterCard("outlaw cards"); + + static { + filter.add(OutlawPredicate.instance); + } + + @Override + public void adjustTargets(Ability ability, Game game) { + ability.getTargets().clear(); + ability.addTarget(new TargetCardInYourGraveyard(ability.getManaCostsToPay().getX(), filter)); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BadlandsRevival.java b/Mage.Sets/src/mage/cards/b/BadlandsRevival.java new file mode 100644 index 00000000000..e7df8c51fec --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BadlandsRevival.java @@ -0,0 +1,41 @@ +package mage.cards.b; + +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.FilterCard; +import mage.filter.StaticFilters; +import mage.filter.common.FilterPermanentCard; +import mage.target.common.TargetCardInYourGraveyard; +import mage.target.targetpointer.SecondTargetPointer; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class BadlandsRevival extends CardImpl { + + private static final FilterCard filter = new FilterPermanentCard("permanent card from your graveyard"); + + public BadlandsRevival(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{B}{G}"); + + // Return up to one target creature card from your graveyard to the battlefield. Return up to one target permanent card from your graveyard to your hand. + this.getSpellAbility().addEffect(new ReturnFromGraveyardToBattlefieldTargetEffect()); + this.getSpellAbility().addEffect(new ReturnFromGraveyardToHandTargetEffect().setTargetPointer(new SecondTargetPointer())); + this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(0, 1, StaticFilters.FILTER_CARD_CREATURE_YOUR_GRAVEYARD)); + this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(0, 1, filter)); + } + + private BadlandsRevival(final BadlandsRevival card) { + super(card); + } + + @Override + public BadlandsRevival copy() { + return new BadlandsRevival(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BanditsHaul.java b/Mage.Sets/src/mage/cards/b/BanditsHaul.java new file mode 100644 index 00000000000..2a3a6361f0f --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BanditsHaul.java @@ -0,0 +1,52 @@ +package mage.cards.b; + +import mage.abilities.Ability; +import mage.abilities.common.CommittedCrimeTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.RemoveCountersSourceCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.mana.AnyColorManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.counters.CounterType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BanditsHaul extends CardImpl { + + public BanditsHaul(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}"); + + // Whenever you commit a crime, put a loot counter on Bandit's Haul. This ability triggers only once each turn. + this.addAbility(new CommittedCrimeTriggeredAbility( + new AddCountersSourceEffect(CounterType.LOOT.createInstance()) + ).setTriggersOnceEachTurn(true)); + + // {T}: Add one mana of any color. + this.addAbility(new AnyColorManaAbility()); + + // {2}, {T}, Remove two loot counters from Bandit's Haul: Draw a card. + Ability ability = new SimpleActivatedAbility( + new DrawCardSourceControllerEffect(1), new GenericManaCost(2) + ); + ability.addCost(new TapSourceCost()); + ability.addCost(new RemoveCountersSourceCost(CounterType.LOOT.createInstance(2))); + this.addAbility(ability); + } + + private BanditsHaul(final BanditsHaul card) { + super(card); + } + + @Override + public BanditsHaul copy() { + return new BanditsHaul(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BaronBertramGraywater.java b/Mage.Sets/src/mage/cards/b/BaronBertramGraywater.java new file mode 100644 index 00000000000..d699e5cc0ed --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BaronBertramGraywater.java @@ -0,0 +1,63 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldOneOrMoreTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.mana.ManaCostsImpl; +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.SubType; +import mage.constants.SuperType; +import mage.constants.TargetController; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.game.permanent.token.VampireRogueToken; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class BaronBertramGraywater extends CardImpl { + + static final FilterPermanent filter = new FilterPermanent("tokens"); + + static { + filter.add(TokenPredicate.TRUE); + } + + public BaronBertramGraywater(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.VAMPIRE); + this.subtype.add(SubType.NOBLE); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // Whenever one or more tokens enter the battlefield under your control, create a 1/1 black Vampire Rogue creature token with lifelink. This ability triggers only once each turn. + this.addAbility(new EntersBattlefieldOneOrMoreTriggeredAbility( + new CreateTokenEffect(new VampireRogueToken()), filter, TargetController.YOU + ).setTriggersOnceEachTurn(true)); + + // {1}{B}, Sacrifice another creature or artifact: Draw a card. + Ability ability = new SimpleActivatedAbility(new DrawCardSourceControllerEffect(1), new ManaCostsImpl<>("{1}{B}")); + ability.addCost(new SacrificeTargetCost(StaticFilters.FILTER_CONTROLLED_ANOTHER_CREATURE_OR_ARTIFACT_SHORT_TEXT)); + this.addAbility(ability); + } + + private BaronBertramGraywater(final BaronBertramGraywater card) { + super(card); + } + + @Override + public BaronBertramGraywater copy() { + return new BaronBertramGraywater(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BasandraBattleSeraph.java b/Mage.Sets/src/mage/cards/b/BasandraBattleSeraph.java index 44eb59b16af..57b3ee98727 100644 --- a/Mage.Sets/src/mage/cards/b/BasandraBattleSeraph.java +++ b/Mage.Sets/src/mage/cards/b/BasandraBattleSeraph.java @@ -59,7 +59,7 @@ public final class BasandraBattleSeraph extends CardImpl { class BasandraBattleSeraphEffect extends ContinuousRuleModifyingEffectImpl { public BasandraBattleSeraphEffect() { - super(Duration.EndOfTurn, Outcome.Neutral); + super(Duration.WhileOnBattlefield, Outcome.Neutral); staticText = "Players can't cast spells during combat"; } diff --git a/Mage.Sets/src/mage/cards/b/BatColony.java b/Mage.Sets/src/mage/cards/b/BatColony.java index fc932b06b36..e9eebef52a2 100644 --- a/Mage.Sets/src/mage/cards/b/BatColony.java +++ b/Mage.Sets/src/mage/cards/b/BatColony.java @@ -99,10 +99,11 @@ enum BatColonyValue implements DynamicValue { /** * Inspired by {@link mage.watchers.common.ManaPaidSourceWatcher} - * If more cards like Bat Colony care for mana spent by Caves in the future, best to refactor the tracking there. + * If more cards like Bat Colony care for mana produced by Caves in the future, best to refactor the tracking there. * For now the assumption is that it is a 1of, so don't want to track it in any game. */ class BatColonyWatcher extends Watcher { + private static final class CaveManaPaidTracker implements Serializable, Copyable { private int caveMana = 0; diff --git a/Mage.Sets/src/mage/cards/b/BattleOfHooverDam.java b/Mage.Sets/src/mage/cards/b/BattleOfHooverDam.java new file mode 100644 index 00000000000..9715cff968d --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BattleOfHooverDam.java @@ -0,0 +1,78 @@ +package mage.cards.b; + +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.common.AsEntersBattlefieldAbility; +import mage.abilities.common.BeginningOfEndStepTriggeredAbility; +import mage.abilities.common.DiesCreatureTriggeredAbility; +import mage.abilities.condition.common.ModeChoiceSourceCondition; +import mage.abilities.decorator.ConditionalTriggeredAbility; +import mage.abilities.effects.common.ChooseModeEffect; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldWithCounterTargetEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.TargetController; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreatureCard; +import mage.filter.predicate.mageobject.ManaValuePredicate; +import mage.target.common.TargetCardInYourGraveyard; +import mage.target.common.TargetControlledCreaturePermanent; + +/** + * @author Cguy7777 + */ +public final class BattleOfHooverDam extends CardImpl { + + private static final FilterCreatureCard filter = new FilterCreatureCard(); + + static { + filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, 4)); + } + + public BattleOfHooverDam(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{W}"); + + // As Battle of Hoover Dam enters the battlefield, choose NCR or Legion. + this.addAbility(new AsEntersBattlefieldAbility( + new ChooseModeEffect("NCR or Legion?", "NCR", "Legion"))); + + // * NCR -- At the beginning of your end step, return target creature card with mana value 3 or less + // from your graveyard to the battlefield with a finality counter on it. + Ability ncrAbility = new ConditionalTriggeredAbility( + new BeginningOfEndStepTriggeredAbility( + new ReturnFromGraveyardToBattlefieldWithCounterTargetEffect(CounterType.FINALITY.createInstance()), + TargetController.YOU, + false), + new ModeChoiceSourceCondition("NCR"), + "&bull NCR — At the beginning of your end step, return target creature card with " + + "mana value 3 or less from your graveyard to the battlefield with a finality counter on it."); + ncrAbility.addTarget(new TargetCardInYourGraveyard(filter)); + this.addAbility(ncrAbility); + + // * Legion -- Whenever a creature you control dies, put two +1/+1 counters on target creature you control. + Ability legionAbility = new ConditionalTriggeredAbility( + new DiesCreatureTriggeredAbility( + new AddCountersTargetEffect(CounterType.P1P1.createInstance(2)), + false, + StaticFilters.FILTER_CONTROLLED_A_CREATURE), + new ModeChoiceSourceCondition("Legion"), + "&bull Legion — Whenever a creature you control dies, " + + "put two +1/+1 counters on target creature you control."); + legionAbility.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(legionAbility); + } + + private BattleOfHooverDam(final BattleOfHooverDam card) { + super(card); + } + + @Override + public BattleOfHooverDam copy() { + return new BattleOfHooverDam(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BeastbondOutcaster.java b/Mage.Sets/src/mage/cards/b/BeastbondOutcaster.java new file mode 100644 index 00000000000..e94e6fa3e56 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BeastbondOutcaster.java @@ -0,0 +1,49 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.common.FerociousCondition; +import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.hint.common.FerociousHint; +import mage.abilities.keyword.PlotAbility; +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 BeastbondOutcaster extends CardImpl { + + public BeastbondOutcaster(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.DRUID); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // When Beastbond Outcaster enters the battlefield, if you control a creature with power 4 or greater, draw a card. + this.addAbility(new ConditionalInterveningIfTriggeredAbility( + new EntersBattlefieldTriggeredAbility(new DrawCardSourceControllerEffect(1)), + FerociousCondition.instance, "When {this} enters the battlefield, " + + "if you control a creature with power 4 or greater, draw a card." + ).addHint(FerociousHint.instance)); + + // Plot {1}{G} + this.addAbility(new PlotAbility("{1}{G}")); + } + + private BeastbondOutcaster(final BeastbondOutcaster card) { + super(card); + } + + @Override + public BeastbondOutcaster copy() { + return new BeastbondOutcaster(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BehemothOfVault.java b/Mage.Sets/src/mage/cards/b/BehemothOfVault.java new file mode 100644 index 00000000000..f947959e20c --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BehemothOfVault.java @@ -0,0 +1,112 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DiesSourceTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.common.PayEnergyCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.effects.common.counter.GetEnergyCountersControllerEffect; +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.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetNonlandPermanent; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; + +/** + * @author notgreat + */ +public final class BehemothOfVault extends CardImpl { + + public BehemothOfVault(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{6}"); + + this.subtype.add(SubType.ROBOT); + this.power = new MageInt(6); + this.toughness = new MageInt(6); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // When Behemoth of Vault 0 enters the battlefield, you get {E}{E}{E}{E}. + this.addAbility(new EntersBattlefieldTriggeredAbility(new GetEnergyCountersControllerEffect(4))); + + // When Behemoth of Vault 0 dies, you may pay an amount of {E} equal to target nonland permanent's mana value. When you do, destroy that permanent. + Ability ability = new DiesSourceTriggeredAbility(new BehemothOfVaultDoWhenCostPaid( + new ReflexiveTriggeredAbility(new DestroyTargetEffect().setText("destroy that permanent"), false))); + ability.addTarget(new TargetNonlandPermanent()); + this.addAbility(ability); + } + + private BehemothOfVault(final BehemothOfVault card) { + super(card); + } + + @Override + public BehemothOfVault copy() { + return new BehemothOfVault(this); + } +} + +//Based on DoWhenCostPaid but constructs the cost itself and uses a fixed target for the effect +class BehemothOfVaultDoWhenCostPaid extends OneShotEffect { + + private final ReflexiveTriggeredAbility ability; + + BehemothOfVaultDoWhenCostPaid(ReflexiveTriggeredAbility ability) { + super(Outcome.Benefit); + this.ability = ability; + this.staticText = "you may pay an amount of {E} equal to target nonland permanent's mana value. When you do, destroy that permanent."; + } + + BehemothOfVaultDoWhenCostPaid(final BehemothOfVaultDoWhenCostPaid effect) { + super(effect); + this.ability = effect.ability.copy(); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Permanent targetPermanent = game.getPermanent(source.getFirstTarget()); + if (targetPermanent == null || player == null) { + return false; + } + Cost cost = new PayEnergyCost(targetPermanent.getManaValue()); + if (!cost.canPay(source, source, player.getId(), game) + || !player.chooseUse(Outcome.DestroyPermanent, cost.getText(), source, game)) { + return true; + } + cost.clearPaid(); + int bookmark = game.bookmarkState(); + if (cost.pay(source, game, source, player.getId(), false)) { + ability.getEffects().setTargetPointer(new FixedTarget(targetPermanent, game)); + game.fireReflexiveTriggeredAbility(ability, source); + player.resetStoredBookmark(game); + return true; + } + player.restoreState(bookmark, BehemothOfVaultDoWhenCostPaid.class.getName(), game); + return true; + } + + @Override + public void setValue(String key, Object value) { + super.setValue(key, value); + ability.getEffects().setValue(key, value); + } + + @Override + public BehemothOfVaultDoWhenCostPaid copy() { + return new BehemothOfVaultDoWhenCostPaid(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BenSolo.java b/Mage.Sets/src/mage/cards/b/BenSolo.java index 0cb1245c9fd..5c3ef6b4e19 100644 --- a/Mage.Sets/src/mage/cards/b/BenSolo.java +++ b/Mage.Sets/src/mage/cards/b/BenSolo.java @@ -2,27 +2,24 @@ package mage.cards.b; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.common.DealtDamageToSourceTriggeredAbility; +import mage.abilities.dynamicvalue.common.SavedDamageValue; +import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.keyword.VigilanceAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; -import mage.game.Game; -import mage.game.events.DamagedEvent; -import mage.game.events.DamagedBatchForPermanentsEvent; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; -import mage.players.Player; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; import mage.target.common.TargetPlayerOrPlaneswalker; -import java.util.Set; import java.util.UUID; /** * @author Merlingilb */ public class BenSolo extends CardImpl { + public BenSolo(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R/W}{W}"); this.supertype.add(SuperType.LEGENDARY); @@ -31,11 +28,14 @@ public class BenSolo extends CardImpl { this.power = new MageInt(4); this.toughness = new MageInt(4); - //Vigilance + // Vigilance this.addAbility(VigilanceAbility.getInstance()); - //Whenever Ben Solo is dealt damage, it deals that much damage to target player or planeswalker. - this.addAbility(new BenSoloTriggeredAbility()); + // Whenever Ben Solo is dealt damage, it deals that much damage to target player or planeswalker. + Ability ability = new DealtDamageToSourceTriggeredAbility( + new DamageTargetEffect(SavedDamageValue.MUCH, "it"), false); + ability.addTarget(new TargetPlayerOrPlaneswalker()); + this.addAbility(ability); } private BenSolo(final BenSolo card) { @@ -47,103 +47,3 @@ public class BenSolo extends CardImpl { return new BenSolo(this); } } - -class BenSoloTriggeredAbility extends TriggeredAbilityImpl { - - UUID benSoloID; - - BenSoloTriggeredAbility() { - super(Zone.BATTLEFIELD, new BenSoloEffect(), false); - this.addTarget(new TargetPlayerOrPlaneswalker()); - benSoloID = this.getSourceId(); - } - - private BenSoloTriggeredAbility(final BenSoloTriggeredAbility ability) { - super(ability); - benSoloID = ability.benSoloID; - } - - @Override - public BenSoloTriggeredAbility copy() { - return new BenSoloTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_PERMANENTS; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (benSoloID == null) { - benSoloID = this.getSourceId(); - if (benSoloID == null) { - return false; - } - } - - int damage = 0; - DamagedBatchForPermanentsEvent dEvent = (DamagedBatchForPermanentsEvent) event; - Set set = dEvent.getEvents(); - for (DamagedEvent damagedEvent : set) { - UUID targetID = damagedEvent.getTargetId(); - if (targetID == null) { - continue; - } - - if (targetID == benSoloID) { - damage += damagedEvent.getAmount(); - } - } - - if (damage > 0) { - this.getEffects().setValue("damage", damage); - this.getEffects().setValue("benSoloID", benSoloID); - return true; - } - return false; - } - - @Override - public String getRule() { - return "Whenever Ben Solo is dealt damage, it deals that much damage to target player or planeswalker."; - } -} - -class BenSoloEffect extends OneShotEffect { - - BenSoloEffect() { - super(Outcome.Benefit); - } - - private BenSoloEffect(final BenSoloEffect effect) { - super(effect); - } - - @Override - public BenSoloEffect copy() { - return new BenSoloEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Integer damage = (Integer)getValue("damage"); - UUID benSoloID = (UUID)getValue("benSoloID"); - - if (benSoloID == null || damage == null || damage < 1) { - return false; - } - - Permanent permanent = game.getPermanent(source.getFirstTarget()); - if (permanent != null) { - permanent.damage(damage, benSoloID, source, game); - return true; - } - Player player = game.getPlayer(source.getFirstTarget()); - if (player != null) { - player.damage(damage, benSoloID, source, game); - return true; - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/b/BetrayalAtTheVault.java b/Mage.Sets/src/mage/cards/b/BetrayalAtTheVault.java new file mode 100644 index 00000000000..206c7c0f4ee --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BetrayalAtTheVault.java @@ -0,0 +1,84 @@ +package mage.cards.b; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.other.AnotherTargetPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.Target; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetCreaturePermanent; + +import java.util.List; +import java.util.Objects; +import java.util.UUID; + +/** + * @author Susucr + */ +public final class BetrayalAtTheVault extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("other target creatures"); + + static { + filter.add(new AnotherTargetPredicate(2)); + } + + public BetrayalAtTheVault(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{4}{G}{G}"); + + // Target creature you control deals damage equal to its power to each of two other target creatures. + this.getSpellAbility().addEffect(new BetrayalAtTheVaultEffect()); + this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent().setTargetTag(1)); + this.getSpellAbility().addTarget(new TargetCreaturePermanent(2, 2, filter, false).setTargetTag(2)); + } + + private BetrayalAtTheVault(final BetrayalAtTheVault card) { + super(card); + } + + @Override + public BetrayalAtTheVault copy() { + return new BetrayalAtTheVault(this); + } +} + +class BetrayalAtTheVaultEffect extends OneShotEffect { + + BetrayalAtTheVaultEffect() { + super(Outcome.Benefit); + staticText = "Target creature you control deals damage equal to its power " + + "to each of two other target creatures"; + } + + private BetrayalAtTheVaultEffect(final BetrayalAtTheVaultEffect effect) { + super(effect); + } + + @Override + public BetrayalAtTheVaultEffect copy() { + return new BetrayalAtTheVaultEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent creature = game.getPermanent(source.getFirstTarget()); + if (creature == null) { + return false; + } + source.getTargets() + .stream() + .filter(t -> t.getTargetTag() == 2) + .map(Target::getTargets) + .flatMap(List::stream) + .map(game::getPermanent) + .filter(Objects::nonNull) + .forEach(p -> p.damage(creature.getPower().getValue(), creature.getId(), source, game)); + return true; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/b/BiggerOnTheInside.java b/Mage.Sets/src/mage/cards/b/BiggerOnTheInside.java new file mode 100644 index 00000000000..f91c4b148b5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BiggerOnTheInside.java @@ -0,0 +1,106 @@ +package mage.cards.b; + +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.effects.common.continuous.NextSpellCastHasAbilityEffect; +import mage.abilities.effects.mana.AddManaOfAnyColorEffect; +import mage.abilities.effects.mana.ManaEffect; +import mage.abilities.keyword.CascadeAbility; +import mage.abilities.keyword.EnchantAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.predicate.Predicates; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.TargetPlayer; + +import java.util.UUID; + +/** + * + * @author notgreat + */ +public final class BiggerOnTheInside extends CardImpl { + + private static final FilterPermanent filter + = new FilterPermanent("artifact or land"); + + static { + filter.add(Predicates.or( + CardType.ARTIFACT.getPredicate(), + CardType.LAND.getPredicate() + )); + } + + public BiggerOnTheInside(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{R}{G}"); + this.subtype.add(SubType.AURA); + + // Enchant artifact or land + TargetPermanent auraTarget = new TargetPermanent(filter); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.AddAbility)); + this.addAbility(new EnchantAbility(auraTarget)); + + // Enchanted permanent has "{T}: Target player adds two mana of any one color. The next spell they cast this turn has cascade." + Ability gainedAbility = new SimpleActivatedAbility(Zone.BATTLEFIELD, new BiggerOnTheInsideEffect(), new TapSourceCost()); + gainedAbility.addTarget(new TargetPlayer()); + Effect effect = new GainAbilityAttachedEffect(gainedAbility, AttachmentType.AURA, Duration.WhileOnBattlefield, null, "permanent"); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, effect)); + } + + private BiggerOnTheInside(final BiggerOnTheInside card) { + super(card); + } + + @Override + public BiggerOnTheInside copy() { + return new BiggerOnTheInside(this); + } +} + +class BiggerOnTheInsideEffect extends OneShotEffect { //Not a mana ability since it targets + + BiggerOnTheInsideEffect() { + super(Outcome.Benefit); + staticText = "Target player adds two mana of any one color. The next spell they cast this turn has cascade"; + } + + private BiggerOnTheInsideEffect(final BiggerOnTheInsideEffect effect) { + super(effect); + } + + @Override + public BiggerOnTheInsideEffect copy() { + return new BiggerOnTheInsideEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); + if (player == null) { + return false; + } + ContinuousEffect cascadeEffect = new NextSpellCastHasAbilityEffect(new CascadeAbility(), StaticFilters.FILTER_CARD, TargetController.SOURCE_TARGETS); + game.addEffect(cascadeEffect, source); + + ManaEffect manaEffect = new AddManaOfAnyColorEffect(2); + Mana manaToAdd = manaEffect.produceMana(game, source); + if (manaToAdd != null && manaToAdd.count() > 0) { + player.getManaPool().addMana(manaToAdd, game, source); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/b/BighornerRancher.java b/Mage.Sets/src/mage/cards/b/BighornerRancher.java new file mode 100644 index 00000000000..270ebcb43d8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BighornerRancher.java @@ -0,0 +1,57 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.Mana; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.dynamicvalue.common.GreatestPowerAmongControlledCreaturesValue; +import mage.abilities.dynamicvalue.common.GreatestToughnessAmongControlledCreaturesValue; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.keyword.VigilanceAbility; +import mage.abilities.mana.DynamicManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Zone; + +import java.util.UUID; + +/** + * @author notgreat + */ +public final class BighornerRancher extends CardImpl { + + public BighornerRancher(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.RANGER); + this.power = new MageInt(2); + this.toughness = new MageInt(5); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // {T}: Add an amount of {G} equal to the greatest power among creatures you control. + this.addAbility(new DynamicManaAbility( + Mana.GreenMana(1), GreatestPowerAmongControlledCreaturesValue.instance, new TapSourceCost(), + "Add an amount of {G} equal to the greatest power among creatures you control." + )); + + // Sacrifice Bighorner Rancher: You gain life equal to the greatest toughness among other creatures you control. + this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, + new GainLifeEffect(GreatestToughnessAmongControlledCreaturesValue.instance).setText("You gain life equal to the greatest toughness among other creatures you control."), + new SacrificeSourceCost())); + } + + private BighornerRancher(final BighornerRancher card) { + super(card); + } + + @Override + public BighornerRancher copy() { + return new BighornerRancher(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BindingNegotiation.java b/Mage.Sets/src/mage/cards/b/BindingNegotiation.java new file mode 100644 index 00000000000..6f5ac383246 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BindingNegotiation.java @@ -0,0 +1,106 @@ +package mage.cards.b; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.*; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.filter.common.FilterNonlandCard; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.card.FaceDownPredicate; +import mage.filter.predicate.card.OwnerIdPredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInExile; +import mage.target.common.TargetOpponent; + +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * @author Susucr + */ +public final class BindingNegotiation extends CardImpl { + + public BindingNegotiation(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{B}"); + + // Target opponent reveals their hand. You may choose a nonland card from it. If you do, they discard it. Otherwise, you may put a face-up exiled card they own into their graveyard. + this.getSpellAbility().addEffect(new BindingNegotiationEffect()); + this.getSpellAbility().addTarget(new TargetOpponent()); + } + + private BindingNegotiation(final BindingNegotiation card) { + super(card); + } + + @Override + public BindingNegotiation copy() { + return new BindingNegotiation(this); + } +} + +class BindingNegotiationEffect extends OneShotEffect { + + BindingNegotiationEffect() { + super(Outcome.Discard); + staticText = "Target opponent reveals their hand. You may choose a nonland card from it. " + + "If you do, they discard it. Otherwise, you may put a face-up exiled card they own into their graveyard"; + } + + private BindingNegotiationEffect(final BindingNegotiationEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); + Player controller = game.getPlayer(source.getControllerId()); + if (player == null || controller == null) { + return false; + } + // Target opponent reveals their hand + Cards revealedCards = new CardsImpl(); + revealedCards.addAll(player.getHand()); + player.revealCards(source, revealedCards, game); + + // You may choose a nonland card from it. + TargetCard target = new TargetCard(0, 1, Zone.HAND, new FilterNonlandCard()); + controller.choose(outcome, revealedCards, target, source, game); + + UUID chosenId = target.getFirstTarget(); + if (chosenId != null) { + // If you do, they discard it. + Card card = revealedCards.get(target.getFirstTarget(), game); + player.discard(card, false, source, game); + } else { + // Otherwise, you may put a face-up exiled card they own into their graveyard. + FilterCard filter = new FilterCard("face-up exiled card owned by " + player.getName()); + filter.add(Predicates.not(FaceDownPredicate.instance)); + filter.add(new OwnerIdPredicate(player.getId())); + TargetCard targetExiled = new TargetCardInExile(0, 1, filter, null); + controller.choose(outcome, targetExiled, source, game); + Set chosenExiledCard = targetExiled + .getTargets() + .stream() + .map(game::getCard) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + if (!chosenExiledCard.isEmpty()) { + player.moveCards(chosenExiledCard, Zone.GRAVEYARD, source, game); + } + } + return true; + } + + @Override + public BindingNegotiationEffect copy() { + return new BindingNegotiationEffect(this); + } + +} diff --git a/Mage.Sets/src/mage/cards/b/BiteDownOnCrime.java b/Mage.Sets/src/mage/cards/b/BiteDownOnCrime.java new file mode 100644 index 00000000000..98e460eadb2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BiteDownOnCrime.java @@ -0,0 +1,69 @@ +package mage.cards.b; + +import mage.abilities.Ability; +import mage.abilities.condition.common.CollectedEvidenceCondition; +import mage.abilities.costs.CostAdjuster; +import mage.abilities.costs.OptionalAdditionalCost; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.DamageWithPowerFromOneToAnotherTargetEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.keyword.CollectEvidenceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetCreaturePermanent; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * + * @author notgreat + */ +public final class BiteDownOnCrime extends CardImpl { + + public BiteDownOnCrime(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{G}"); + + // As an additional cost to cast this spell, you may collect evidence 6. This spell costs {2} less to cast if evidence was collected. + this.addAbility(new CollectEvidenceAbility(6,"This spell costs {2} less to cast if evidence was collected")); + this.getSpellAbility().setCostAdjuster(BiteDownOnCrimeAdjuster.instance); + + // Target creature you control gets +2/+0 until end of turn. + Effect effect = new BoostTargetEffect(2, 0, Duration.EndOfTurn); + this.getSpellAbility().addEffect(effect); + this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent()); + + // It deals damage equal to its power to target creature you don't control. + this.getSpellAbility().addEffect(new DamageWithPowerFromOneToAnotherTargetEffect("It")); + this.getSpellAbility().addTarget(new TargetCreaturePermanent(StaticFilters.FILTER_CREATURE_YOU_DONT_CONTROL)); + } + + private BiteDownOnCrime(final BiteDownOnCrime card) { + super(card); + } + + @Override + public BiteDownOnCrime copy() { + return new BiteDownOnCrime(this); + } +} + + +enum BiteDownOnCrimeAdjuster implements CostAdjuster { + instance; + + private static final OptionalAdditionalCost collectEvidenceCost = CollectEvidenceAbility.makeCost(6); + + @Override + public void adjustCosts(Ability ability, Game game) { + if (CollectedEvidenceCondition.instance.apply(game, ability) + || (game.inCheckPlayableState() && collectEvidenceCost.canPay(ability, null, ability.getControllerId(), game))) { + CardUtil.reduceCost(ability, 2); + } + } +} diff --git a/Mage.Sets/src/mage/cards/b/BlacksnagBuzzard.java b/Mage.Sets/src/mage/cards/b/BlacksnagBuzzard.java new file mode 100644 index 00000000000..2e98119b783 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BlacksnagBuzzard.java @@ -0,0 +1,51 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.condition.common.MorbidCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.hint.common.MorbidHint; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.PlotAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BlacksnagBuzzard extends CardImpl { + + public BlacksnagBuzzard(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); + + this.subtype.add(SubType.BIRD); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Blacksnag Buzzard enters the battlefield with a +1/+1 counter on it if a creature died this turn. + this.addAbility(new EntersBattlefieldAbility(new ConditionalOneShotEffect( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), MorbidCondition.instance, "" + ), "with a +1/+1 counter on it if a creature died this turn").addHint(MorbidHint.instance)); + + // Plot {1}{B} + this.addAbility(new PlotAbility("{1}{B}")); + } + + private BlacksnagBuzzard(final BlacksnagBuzzard card) { + super(card); + } + + @Override + public BlacksnagBuzzard copy() { + return new BlacksnagBuzzard(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BlatantThievery.java b/Mage.Sets/src/mage/cards/b/BlatantThievery.java index c619988e8f5..c2b6fe4723b 100644 --- a/Mage.Sets/src/mage/cards/b/BlatantThievery.java +++ b/Mage.Sets/src/mage/cards/b/BlatantThievery.java @@ -1,18 +1,12 @@ package mage.cards.b; -import mage.abilities.Ability; import mage.abilities.effects.common.continuous.GainControlTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; -import mage.filter.FilterPermanent; -import mage.filter.StaticFilters; -import mage.filter.predicate.permanent.ControllerIdPredicate; -import mage.game.Game; -import mage.players.Player; import mage.target.TargetPermanent; -import mage.target.targetadjustment.TargetAdjuster; +import mage.target.targetadjustment.EachOpponentPermanentTargetsAdjuster; import mage.target.targetpointer.EachTargetPointer; import java.util.UUID; @@ -29,7 +23,7 @@ public final class BlatantThievery extends CardImpl { this.getSpellAbility().addEffect(new GainControlTargetEffect(Duration.Custom, true) .setTargetPointer(new EachTargetPointer()) .setText("for each opponent, gain control of target permanent that player controls")); - this.getSpellAbility().setTargetAdjuster(BlatantThieveryAdjuster.instance); + this.getSpellAbility().setTargetAdjuster(new EachOpponentPermanentTargetsAdjuster(new TargetPermanent())); } private BlatantThievery(final BlatantThievery card) { @@ -41,25 +35,3 @@ public final class BlatantThievery extends CardImpl { return new BlatantThievery(this); } } - -enum BlatantThieveryAdjuster implements TargetAdjuster { - instance; - - @Override - public void adjustTargets(Ability ability, Game game) { - ability.getTargets().clear(); - for (UUID opponentId : game.getOpponents(ability.getControllerId())) { - Player opponent = game.getPlayer(opponentId); - if (opponent == null || game.getBattlefield().count( - StaticFilters.FILTER_CONTROLLED_PERMANENT, - opponentId, ability, game - ) < 1) { - continue; - } - FilterPermanent filter = new FilterPermanent("Permanent controlled by " + opponent.getName()); - filter.add(new ControllerIdPredicate(opponentId)); - TargetPermanent targetPermanent = new TargetPermanent(filter); - ability.addTarget(targetPermanent); - } - } -} diff --git a/Mage.Sets/src/mage/cards/b/BloodHound.java b/Mage.Sets/src/mage/cards/b/BloodHound.java index c7cfffaea0a..942a67c4953 100644 --- a/Mage.Sets/src/mage/cards/b/BloodHound.java +++ b/Mage.Sets/src/mage/cards/b/BloodHound.java @@ -67,7 +67,7 @@ class BloodHoundTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PLAYER; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PLAYER; } @Override diff --git a/Mage.Sets/src/mage/cards/b/BloodHustler.java b/Mage.Sets/src/mage/cards/b/BloodHustler.java new file mode 100644 index 00000000000..7edd5685fc4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BloodHustler.java @@ -0,0 +1,53 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.CommittedCrimeTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.LoseLifeTargetEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BloodHustler extends CardImpl { + + public BloodHustler(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}"); + + this.subtype.add(SubType.VAMPIRE); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Whenever you commit a crime, put a +1/+1 counter on Blood Hustler. This ability triggers only once each turn. + this.addAbility(new CommittedCrimeTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()) + ).setTriggersOnceEachTurn(true)); + + // {3}{B}: Target opponent loses 1 life and you gain 1 life. + Ability ability = new SimpleActivatedAbility(new LoseLifeTargetEffect(1), new ManaCostsImpl<>("{3}{B}")); + ability.addEffect(new GainLifeEffect(1).concatBy("and")); + ability.addTarget(new TargetOpponent()); + this.addAbility(ability); + } + + private BloodHustler(final BloodHustler card) { + super(card); + } + + @Override + public BloodHustler copy() { + return new BloodHustler(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BloodSpatterAnalysis.java b/Mage.Sets/src/mage/cards/b/BloodSpatterAnalysis.java index 764b63a052d..592cb644a0c 100644 --- a/Mage.Sets/src/mage/cards/b/BloodSpatterAnalysis.java +++ b/Mage.Sets/src/mage/cards/b/BloodSpatterAnalysis.java @@ -85,9 +85,7 @@ class BloodSpatterAnalysisTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { - ZoneChangeBatchEvent zBatchEvent = (ZoneChangeBatchEvent) event; - - for (ZoneChangeEvent zEvent : zBatchEvent.getEvents()) { + for (ZoneChangeEvent zEvent : ((ZoneChangeBatchEvent) event).getEvents()) { if (zEvent.isDiesEvent()) { Permanent permanent = game.getPermanentOrLKIBattlefield(zEvent.getTargetId()); if (permanent != null && permanent.isCreature(game)) { diff --git a/Mage.Sets/src/mage/cards/b/BoneyardDesecrator.java b/Mage.Sets/src/mage/cards/b/BoneyardDesecrator.java new file mode 100644 index 00000000000..e45df1bece6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BoneyardDesecrator.java @@ -0,0 +1,104 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenEffect; +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.Zone; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.predicate.mageobject.OutlawPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.TreasureToken; + +import java.util.List; +import java.util.UUID; + +/** + * @author Susucr + */ +public final class BoneyardDesecrator extends CardImpl { + + public BoneyardDesecrator(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}"); + + this.subtype.add(SubType.ZOMBIE); + this.subtype.add(SubType.MERCENARY); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // Menace + this.addAbility(new MenaceAbility(false)); + + // {1}{B}, Sacrifice another creature: Put a +1/+1 counter on Boneyard Desecrator. If an outlaw was sacrificed this way, create a Treasure token. + Effect effect = new AddCountersSourceEffect(CounterType.P1P1.createInstance()); + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, effect, new ManaCostsImpl<>("{1}{B}")); + ability.addCost(new SacrificeTargetCost(StaticFilters.FILTER_CONTROLLED_ANOTHER_CREATURE)); + ability.addEffect(new BoneyardDesecratorEffect()); + this.addAbility(ability); + } + + private BoneyardDesecrator(final BoneyardDesecrator card) { + super(card); + } + + @Override + public BoneyardDesecrator copy() { + return new BoneyardDesecrator(this); + } +} + +// Inspired by Thallid Omnivore +class BoneyardDesecratorEffect extends OneShotEffect { + + private static final FilterPermanent filter = new FilterPermanent("an outlaw"); + + static { + filter.add(OutlawPredicate.instance); + } + + BoneyardDesecratorEffect() { + super(Outcome.GainLife); + this.staticText = "If an outlaw was sacrificed this way, create a Treasure token"; + } + + private BoneyardDesecratorEffect(final BoneyardDesecratorEffect effect) { + super(effect); + } + + @Override + public BoneyardDesecratorEffect copy() { + return new BoneyardDesecratorEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + for (Cost cost : source.getCosts()) { + if (cost instanceof SacrificeTargetCost) { + SacrificeTargetCost sacrificeCost = (SacrificeTargetCost) cost; + List permanents = sacrificeCost.getPermanents(); + for (Permanent permanent : permanents) { + if (!filter.match(permanent, source.getControllerId(), source, game)) { + continue; + } + return new CreateTokenEffect(new TreasureToken()).apply(game, source); + } + } + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/b/BonnyPallClearcutter.java b/Mage.Sets/src/mage/cards/b/BonnyPallClearcutter.java new file mode 100644 index 00000000000..a4c40acd8a9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BonnyPallClearcutter.java @@ -0,0 +1,55 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksWithCreaturesTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.PutCardFromHandOrGraveyardOntoBattlefieldEffect; +import mage.abilities.keyword.ReachAbility; +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.BeauToken; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class BonnyPallClearcutter extends CardImpl { + + public BonnyPallClearcutter(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}{U}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.GIANT); + this.subtype.add(SubType.SCOUT); + this.power = new MageInt(6); + this.toughness = new MageInt(5); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // When Bonny Pall, Clearcutter enters the battlefield, create Beau, a legendary blue Ox creature token with "This creature's power and toughness are each equal to the number of lands you control." + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new BeauToken()))); + + // Whenever you attack, draw a card, then you may put a land card from your hand or graveyard onto the battlefield. + Ability ability = new AttacksWithCreaturesTriggeredAbility(new DrawCardSourceControllerEffect(1), 1); + ability.addEffect(new PutCardFromHandOrGraveyardOntoBattlefieldEffect(StaticFilters.FILTER_CARD_LAND_A, false).concatBy(", then")); + this.addAbility(ability); + } + + private BonnyPallClearcutter(final BonnyPallClearcutter card) { + super(card); + } + + @Override + public BonnyPallClearcutter copy() { + return new BonnyPallClearcutter(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BoomBox.java b/Mage.Sets/src/mage/cards/b/BoomBox.java new file mode 100644 index 00000000000..190b20452ca --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BoomBox.java @@ -0,0 +1,50 @@ +package mage.cards.b; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.target.common.TargetArtifactPermanent; +import mage.target.common.TargetCreaturePermanent; +import mage.target.common.TargetLandPermanent; +import mage.target.targetpointer.EachTargetPointer; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class BoomBox extends CardImpl { + + public BoomBox(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); + + // {6}, {T}, Sacrifice Boom Box: Destroy up to one target artifact, up to one target creature, and up to one target land. + Ability ability = new SimpleActivatedAbility( + new DestroyTargetEffect("") + .setText("Destroy up to one target artifact, up to one target creature, and up to one target land") + .setTargetPointer(new EachTargetPointer()), + new GenericManaCost(6) + ); + ability.addCost(new TapSourceCost()); + ability.addCost(new SacrificeSourceCost()); + ability.addTarget(new TargetArtifactPermanent(0, 1)); + ability.addTarget(new TargetCreaturePermanent(0, 1)); + ability.addTarget(new TargetLandPermanent(0, 1)); + this.addAbility(ability); + } + + private BoomBox(final BoomBox card) { + super(card); + } + + @Override + public BoomBox copy() { + return new BoomBox(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BottleCapBlast.java b/Mage.Sets/src/mage/cards/b/BottleCapBlast.java new file mode 100644 index 00000000000..6d9d536f495 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BottleCapBlast.java @@ -0,0 +1,84 @@ +package mage.cards.b; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.ImproviseAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.TreasureToken; +import mage.players.Player; +import mage.target.common.TargetAnyTarget; + +import java.util.UUID; + +/** + * @author notgreat + */ +public final class BottleCapBlast extends CardImpl { + + public BottleCapBlast(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{4}{R}"); + + + // Improvise + this.addAbility(new ImproviseAbility()); + + // Bottle-Cap Blast deals 5 damage to any target. If excess damage was dealt to a permanent this way, create that many tapped Treasure tokens. + this.getSpellAbility().addEffect(new BottleCapBlastEffect()); + this.getSpellAbility().addTarget(new TargetAnyTarget()); + } + + private BottleCapBlast(final BottleCapBlast card) { + super(card); + } + + @Override + public BottleCapBlast copy() { + return new BottleCapBlast(this); + } +} + +//Based on Hell to Pay +class BottleCapBlastEffect extends OneShotEffect { + + BottleCapBlastEffect() { + super(Outcome.Benefit); + staticText = "{this} deals 5 damage to any target. " + + "If excess damage was dealt to a permanent this way, create that many tapped Treasure tokens."; + } + + private BottleCapBlastEffect(final BottleCapBlastEffect effect) { + super(effect); + } + + @Override + public BottleCapBlastEffect copy() { + return new BottleCapBlastEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + UUID target = getTargetPointer().getFirst(game, source); + Player player = game.getPlayer(target); + if (player != null) { + player.damage(5, source, game); + return true; + } + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent == null) { + return false; + } + int lethal = Math.min(permanent.getLethalDamage(source.getSourceId(), game), 5); + permanent.damage(5, source.getSourceId(), source, game); + if (lethal < 5) { + new TreasureToken().putOntoBattlefield( + 5 - lethal, game, source, source.getControllerId(), true, false + ); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/b/BoundingFelidar.java b/Mage.Sets/src/mage/cards/b/BoundingFelidar.java new file mode 100644 index 00000000000..b44f359ea49 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BoundingFelidar.java @@ -0,0 +1,65 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksWhileSaddledTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.abilities.keyword.SaddleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class BoundingFelidar extends CardImpl { + + private final static FilterPermanent filter = new FilterControlledCreaturePermanent("other creature you control"); + + static { + filter.add(AnotherPredicate.instance); + } + + private final static DynamicValue xValue = new PermanentsOnBattlefieldCount(filter); + private final static Hint hint = new ValueHint("Other creatures you control", xValue); + + public BoundingFelidar(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{W}"); + + this.subtype.add(SubType.CAT); + this.subtype.add(SubType.BEAST); + this.subtype.add(SubType.MOUNT); + this.power = new MageInt(4); + this.toughness = new MageInt(7); + + // Whenever Bounding Felidar attacks while saddled, put a +1/+1 counter on each other creature you control. You gain 1 life for each of those creatures. + Ability ability = new AttacksWhileSaddledTriggeredAbility(new AddCountersAllEffect(CounterType.P1P1.createInstance(), filter)); + ability.addEffect(new GainLifeEffect(xValue).setText("You gain 1 life for each of those creatures")); + ability.addHint(hint); + this.addAbility(ability); + + // Saddle 2 + this.addAbility(new SaddleAbility(2)); + } + + private BoundingFelidar(final BoundingFelidar card) { + super(card); + } + + @Override + public BoundingFelidar copy() { + return new BoundingFelidar(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BovineIntervention.java b/Mage.Sets/src/mage/cards/b/BovineIntervention.java new file mode 100644 index 00000000000..a314d7ebb34 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BovineIntervention.java @@ -0,0 +1,46 @@ +package mage.cards.b; + +import mage.abilities.effects.common.CreateTokenControllerTargetPermanentEffect; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.FilterPermanent; +import mage.filter.predicate.Predicates; +import mage.game.permanent.token.Ox22Token; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class BovineIntervention extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("artifact or creature"); + + static { + filter.add(Predicates.or( + CardType.ARTIFACT.getPredicate(), + CardType.CREATURE.getPredicate()) + ); + } + + public BovineIntervention(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{W}"); + + // Destroy target artifact or creature. Its controller creates a 2/2 white Ox creature token. + this.getSpellAbility().addTarget(new TargetPermanent(filter)); + this.getSpellAbility().addEffect(new DestroyTargetEffect()); + this.getSpellAbility().addEffect(new CreateTokenControllerTargetPermanentEffect(new Ox22Token())); + } + + private BovineIntervention(final BovineIntervention card) { + super(card); + } + + @Override + public BovineIntervention copy() { + return new BovineIntervention(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BreakOut.java b/Mage.Sets/src/mage/cards/b/BreakOut.java new file mode 100644 index 00000000000..ac9b8c370db --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BreakOut.java @@ -0,0 +1,111 @@ +package mage.cards.b; + +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.HasteAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.PutCards; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author DominionSpy + */ +public final class BreakOut extends CardImpl { + + public BreakOut(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{R}{G}"); + + // Look at the top six cards of your library. You may reveal a creature card from among them. + // If that card has mana value 2 or less, you may put it onto the battlefield and it gains haste until end of turn. + // If you didn't put the revealed card onto the battlefield this way, put it into your hand. + // Put the rest on the bottom of your library in a random order. + this.getSpellAbility().addEffect(new BreakOutEffect()); + } + + private BreakOut(final BreakOut card) { + super(card); + } + + @Override + public BreakOut copy() { + return new BreakOut(this); + } +} + +class BreakOutEffect extends OneShotEffect { + + BreakOutEffect() { + super(Outcome.Benefit); + staticText = "Look at the top six cards of your library. You may reveal a creature card from among them. " + + "If that card has mana value 2 or less, you may put it onto the battlefield and it gains haste until end of turn. " + + "If you didn't put the revealed card onto the battlefield this way, put it into your hand. " + + "Put the rest on the bottom of your library in a random order."; + } + + private BreakOutEffect(final BreakOutEffect effect) { + super(effect); + } + + @Override + public BreakOutEffect copy() { + return new BreakOutEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + + Cards cards = new CardsImpl(controller.getLibrary().getTopCards(game, 6)); + controller.lookAtCards(source, null, cards, game); + + TargetCard target = new TargetCard(0, 1, Zone.LIBRARY, StaticFilters.FILTER_CARD_CREATURE); + target.withChooseHint("Reveal a creature card?"); + if (!controller.chooseTarget(outcome, cards, target, source, game)) { + return PutCards.BOTTOM_RANDOM.moveCards(controller, cards, source, game); + } + + Card pickedCard = game.getCard(target.getFirstTarget()); + if (pickedCard != null) { + controller.revealCards(source, new CardsImpl(pickedCard), game); + cards.remove(pickedCard); + + if (pickedCard.getManaValue() <= 2 && + controller.chooseUse(Outcome.PutCardInPlay, "Put it onto the battlefield?", source, game) && + controller.moveCards(pickedCard, Zone.BATTLEFIELD, source, game)) { + Permanent permanent = game.getPermanent(pickedCard.getId()); + if (permanent != null) { + ContinuousEffect effect = new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.EndOfTurn); + effect.setTargetPointer(new FixedTarget(permanent, game)); + game.addEffect(effect, source); + } + } else { + controller.moveCards(pickedCard, Zone.HAND, source, game); + } + } + + PutCards.BOTTOM_RANDOM.moveCards(controller, cards, source, game); + + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/b/BreechesBrazenPlunderer.java b/Mage.Sets/src/mage/cards/b/BreechesBrazenPlunderer.java index d4a12de49d9..07cdf8199e3 100644 --- a/Mage.Sets/src/mage/cards/b/BreechesBrazenPlunderer.java +++ b/Mage.Sets/src/mage/cards/b/BreechesBrazenPlunderer.java @@ -9,8 +9,8 @@ import mage.abilities.keyword.PartnerAbility; import mage.cards.*; import mage.constants.*; import mage.game.Game; -import mage.game.events.DamagedEvent; import mage.game.events.DamagedBatchForPlayersEvent; +import mage.game.events.DamagedEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.players.Player; @@ -72,9 +72,8 @@ class BreechesBrazenPlundererTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { - DamagedBatchForPlayersEvent dEvent = (DamagedBatchForPlayersEvent) event; Set opponents = new HashSet<>(); - for (DamagedEvent damagedEvent : dEvent.getEvents()) { + for (DamagedEvent damagedEvent : ((DamagedBatchForPlayersEvent) event).getEvents()) { Permanent permanent = game.getPermanent(damagedEvent.getSourceId()); if (permanent == null || !permanent.isControlledBy(getControllerId()) @@ -84,7 +83,7 @@ class BreechesBrazenPlundererTriggeredAbility extends TriggeredAbilityImpl { } opponents.add(damagedEvent.getTargetId()); } - if (opponents.size() < 1) { + if (opponents.isEmpty()) { return false; } this.getEffects().clear(); @@ -148,4 +147,4 @@ class BreechesBrazenPlundererEffect extends OneShotEffect { } return true; } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/b/BreechesTheBlastmaker.java b/Mage.Sets/src/mage/cards/b/BreechesTheBlastmaker.java new file mode 100644 index 00000000000..db5914c2712 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BreechesTheBlastmaker.java @@ -0,0 +1,103 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.CastSecondSpellTriggeredAbility; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CopyStackObjectEffect; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.stack.Spell; +import mage.players.Player; +import mage.target.common.TargetAnyTarget; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BreechesTheBlastmaker extends CardImpl { + + public BreechesTheBlastmaker(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.GOBLIN); + this.subtype.add(SubType.PIRATE); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Menace + this.addAbility(new MenaceAbility(false)); + + // Whenever you cast your second spell each turn, you may sacrifice an artifact. If you do, flip a coin. When you win the flip, copy that spell. You may choose new targets for the copy. When you lose the flip, Breeches, the Blastmaker deals damage equal to that spell's mana value to any target. + this.addAbility(new CastSecondSpellTriggeredAbility(new DoIfCostPaid( + new BreechesTheBlastmakerEffect(), new SacrificeTargetCost(StaticFilters.FILTER_PERMANENT_ARTIFACT_AN) + ))); + } + + private BreechesTheBlastmaker(final BreechesTheBlastmaker card) { + super(card); + } + + @Override + public BreechesTheBlastmaker copy() { + return new BreechesTheBlastmaker(this); + } +} + +class BreechesTheBlastmakerEffect extends OneShotEffect { + + BreechesTheBlastmakerEffect() { + super(Outcome.Benefit); + staticText = "flip a coin. When you win the flip, copy that spell. You may choose new targets for the copy. " + + "When you lose the flip, {this} deals damage equal to that spell's mana value to any target"; + } + + private BreechesTheBlastmakerEffect(final BreechesTheBlastmakerEffect effect) { + super(effect); + } + + @Override + public BreechesTheBlastmakerEffect copy() { + return new BreechesTheBlastmakerEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Spell spell = (Spell) getValue("spellCast"); + ReflexiveTriggeredAbility ability; + if (player.flipCoin(source, game, true)) { + Effect effect = new CopyStackObjectEffect(); + effect.setText("copy that spell. You may choose new targets for the copy"); + effect.setValue("stackObject", spell); + ability = new ReflexiveTriggeredAbility(effect, false); + } else { + int mv = Optional + .ofNullable(spell) + .map(Spell::getManaValue) + .orElse(0); + ability = new ReflexiveTriggeredAbility(new DamageTargetEffect(mv), false); + ability.addTarget(new TargetAnyTarget()); + } + game.fireReflexiveTriggeredAbility(ability, source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/b/BridledBighorn.java b/Mage.Sets/src/mage/cards/b/BridledBighorn.java new file mode 100644 index 00000000000..66cbeb11157 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BridledBighorn.java @@ -0,0 +1,47 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.common.AttacksWhileSaddledTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.SaddleAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.game.permanent.token.SheepWhiteToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BridledBighorn extends CardImpl { + + public BridledBighorn(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); + + this.subtype.add(SubType.SHEEP); + this.subtype.add(SubType.MOUNT); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Whenever Bridled Bighorn attacks while saddled, create a 1/1 white Sheep creature token. + this.addAbility(new AttacksWhileSaddledTriggeredAbility(new CreateTokenEffect(new SheepWhiteToken()))); + + // Saddle 2 + this.addAbility(new SaddleAbility(2)); + } + + private BridledBighorn(final BridledBighorn card) { + super(card); + } + + @Override + public BridledBighorn copy() { + return new BridledBighorn(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BrimstoneRoundup.java b/Mage.Sets/src/mage/cards/b/BrimstoneRoundup.java new file mode 100644 index 00000000000..1a8021e8895 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BrimstoneRoundup.java @@ -0,0 +1,36 @@ +package mage.cards.b; + +import mage.abilities.common.CastSecondSpellTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.PlotAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.game.permanent.token.MercenaryToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BrimstoneRoundup extends CardImpl { + + public BrimstoneRoundup(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{R}"); + + // Whenever you cast your second spell each turn, create a 1/1 red Mercenary creature token with "{T}: Target creature you control gets +1/+0 until end of turn. Activate only as a sorcery." + this.addAbility(new CastSecondSpellTriggeredAbility(new CreateTokenEffect(new MercenaryToken()))); + + // Plot {2}{R} + this.addAbility(new PlotAbility("{2}{R}")); + } + + private BrimstoneRoundup(final BrimstoneRoundup card) { + super(card); + } + + @Override + public BrimstoneRoundup copy() { + return new BrimstoneRoundup(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BristlebudFarmer.java b/Mage.Sets/src/mage/cards/b/BristlebudFarmer.java new file mode 100644 index 00000000000..167583e9e21 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BristlebudFarmer.java @@ -0,0 +1,54 @@ +package mage.cards.b; + +import mage.MageInt; +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.MillThenPutInHandEffect; +import mage.abilities.keyword.TrampleAbility; +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.FoodToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BristlebudFarmer extends CardImpl { + + public BristlebudFarmer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}{G}"); + + this.subtype.add(SubType.PLANT); + this.subtype.add(SubType.DRUID); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // When Bristlebud Farmer enters the battlefield, create two Food tokens. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new FoodToken(), 2))); + + // Whenever Bristlebud Farmer attacks, you may sacrifice a Food. If you do, mill three cards. You may put a permanent card from among them into your hand. + this.addAbility(new AttacksTriggeredAbility(new DoIfCostPaid( + new MillThenPutInHandEffect(3, StaticFilters.FILTER_CARD_A_PERMANENT).withTextOptions("them"), + new SacrificeTargetCost(StaticFilters.FILTER_CONTROLLED_FOOD) + ))); + } + + private BristlebudFarmer(final BristlebudFarmer card) { + super(card); + } + + @Override + public BristlebudFarmer copy() { + return new BristlebudFarmer(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BristlepackSentry.java b/Mage.Sets/src/mage/cards/b/BristlepackSentry.java new file mode 100644 index 00000000000..6af22054d9c --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BristlepackSentry.java @@ -0,0 +1,47 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.FerociousCondition; +import mage.abilities.decorator.ConditionalAsThoughEffect; +import mage.abilities.effects.common.combat.CanAttackAsThoughItDidntHaveDefenderSourceEffect; +import mage.abilities.keyword.DefenderAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BristlepackSentry extends CardImpl { + + public BristlepackSentry(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); + + this.subtype.add(SubType.PLANT); + this.subtype.add(SubType.WOLF); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Defender + this.addAbility(DefenderAbility.getInstance()); + + // As long as you control a creature with power 4 or greater, Bristlepack Sentry can attack as though it didn't have defender. + this.addAbility(new SimpleStaticAbility(new ConditionalAsThoughEffect( + new CanAttackAsThoughItDidntHaveDefenderSourceEffect(Duration.WhileOnBattlefield), FerociousCondition.instance + ).setText("as long as you control a creature with power 4 or greater, {this} can attack as though it didn't have defender"))); + } + + private BristlepackSentry(final BristlepackSentry card) { + super(card); + } + + @Override + public BristlepackSentry copy() { + return new BristlepackSentry(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BristlingBackwoods.java b/Mage.Sets/src/mage/cards/b/BristlingBackwoods.java new file mode 100644 index 00000000000..7415341e98e --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BristlingBackwoods.java @@ -0,0 +1,48 @@ +package mage.cards.b; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTappedAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.mana.GreenManaAbility; +import mage.abilities.mana.RedManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BristlingBackwoods extends CardImpl { + + public BristlingBackwoods(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + this.subtype.add(SubType.DESERT); + + // Bristling Backwoods enters the battlefield tapped. + this.addAbility(new EntersBattlefieldTappedAbility()); + + // When Bristling Backwoods enters the battlefield, it deals 1 damage to target opponent. + Ability ability = new EntersBattlefieldTriggeredAbility(new DamageTargetEffect(1, "it")); + ability.addTarget(new TargetOpponent()); + this.addAbility(ability); + + // {T}: Add {R} or {G}. + this.addAbility(new RedManaAbility()); + this.addAbility(new GreenManaAbility()); + } + + private BristlingBackwoods(final BristlingBackwoods card) { + super(card); + } + + @Override + public BristlingBackwoods copy() { + return new BristlingBackwoods(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BristlyBillSpineSower.java b/Mage.Sets/src/mage/cards/b/BristlyBillSpineSower.java new file mode 100644 index 00000000000..3a884027c34 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BristlyBillSpineSower.java @@ -0,0 +1,54 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.LandfallAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.effects.common.counter.DoubleCounterOnEachPermanentEffect; +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.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BristlyBillSpineSower extends CardImpl { + + public BristlyBillSpineSower(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.PLANT); + this.subtype.add(SubType.DRUID); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Landfall -- Whenever a land enters the battlefield under your control, put a +1/+1 counter on target creature. + Ability ability = new LandfallAbility(new AddCountersTargetEffect(CounterType.P1P1.createInstance())); + ability.addTarget(new TargetCreaturePermanent()); + this.addAbility(ability); + + // {3}{G}{G}: Double the number of +1/+1 counters on each creature you control. + this.addAbility(new SimpleActivatedAbility(new DoubleCounterOnEachPermanentEffect( + CounterType.P1P1, StaticFilters.FILTER_CONTROLLED_CREATURE + ), new ManaCostsImpl<>("{3}{G}{G}"))); + } + + private BristlyBillSpineSower(final BristlyBillSpineSower card) { + super(card); + } + + @Override + public BristlyBillSpineSower copy() { + return new BristlyBillSpineSower(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BronzebeakForagers.java b/Mage.Sets/src/mage/cards/b/BronzebeakForagers.java index a667d2c72f9..da804113374 100644 --- a/Mage.Sets/src/mage/cards/b/BronzebeakForagers.java +++ b/Mage.Sets/src/mage/cards/b/BronzebeakForagers.java @@ -1,6 +1,5 @@ package mage.cards.b; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; @@ -11,22 +10,22 @@ import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.ExileUntilSourceLeavesEffect; import mage.abilities.effects.common.GainLifeEffect; import mage.cards.Card; -import mage.constants.*; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.constants.*; import mage.filter.FilterCard; -import mage.filter.FilterPermanent; -import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.ManaValuePredicate; -import mage.filter.predicate.permanent.ControllerIdPredicate; import mage.game.Game; import mage.players.Player; -import mage.target.TargetPermanent; import mage.target.common.TargetCardInExile; +import mage.target.common.TargetNonlandPermanent; +import mage.target.targetadjustment.EachOpponentPermanentTargetsAdjuster; import mage.target.targetadjustment.TargetAdjuster; import mage.target.targetpointer.EachTargetPointer; import mage.util.CardUtil; +import java.util.UUID; + /** * * @author jimga150 @@ -47,7 +46,7 @@ public final class BronzebeakForagers extends CardImpl { .setTargetPointer(new EachTargetPointer()) .setText("for each opponent, exile up to one target nonland permanent that player controls until {this} leaves the battlefield") ); - etbAbility.setTargetAdjuster(BronzebeakForagerExileAdjuster.instance); + etbAbility.setTargetAdjuster(new EachOpponentPermanentTargetsAdjuster(new TargetNonlandPermanent(0, 1))); this.addAbility(etbAbility); // {X}{W}: Put target card with mana value X exiled with Bronzebeak Foragers into its owner's graveyard. @@ -72,26 +71,6 @@ public final class BronzebeakForagers extends CardImpl { } } -enum BronzebeakForagerExileAdjuster implements TargetAdjuster { - instance; - - @Override - public void adjustTargets(Ability ability, Game game) { - ability.getTargets().clear(); - for (UUID opponentId : game.getOpponents(ability.getControllerId())) { - Player opponent = game.getPlayer(opponentId); - if (opponent == null) { - continue; - } - FilterPermanent filter = new FilterPermanent("nonland permanent controlled by " + opponent.getLogName()); - filter.add(new ControllerIdPredicate(opponentId)); - filter.add(Predicates.not(CardType.LAND.getPredicate())); - TargetPermanent target = new TargetPermanent(0, 1, filter, false); - ability.addTarget(target); - } - } -} - // Based on Gelatinous Cube enum BronzebeakForagerDissolveAdjuster implements TargetAdjuster { instance; diff --git a/Mage.Sets/src/mage/cards/b/BrotherhoodVertibird.java b/Mage.Sets/src/mage/cards/b/BrotherhoodVertibird.java new file mode 100644 index 00000000000..6abb63434c2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BrotherhoodVertibird.java @@ -0,0 +1,48 @@ +package mage.cards.b; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.common.ArtifactYouControlCount; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.continuous.SetBasePowerSourceEffect; +import mage.constants.SubType; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.CrewAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.filter.common.FilterControlledPermanent; + +/** + * + * @author justinjohnson14 + */ +public final class BrotherhoodVertibird extends CardImpl { + public BrotherhoodVertibird(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}"); + + this.subtype.add(SubType.VEHICLE); + this.power = new MageInt(0); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Brotherhood Vertibird's power is equal to the number of artifacts you control. + this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerSourceEffect(ArtifactYouControlCount.instance))); + // Crew 2 + this.addAbility(new CrewAbility(2)); + + } + + private BrotherhoodVertibird(final BrotherhoodVertibird card) { + super(card); + } + + @Override + public BrotherhoodVertibird copy() { + return new BrotherhoodVertibird(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BruseTarlRovingRancher.java b/Mage.Sets/src/mage/cards/b/BruseTarlRovingRancher.java new file mode 100644 index 00000000000..e134ec607d2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BruseTarlRovingRancher.java @@ -0,0 +1,108 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.asthought.PlayFromNotOwnHandZoneTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.DoubleStrikeAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.game.Game; +import mage.game.permanent.token.Ox22Token; +import mage.players.Library; +import mage.players.Player; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class BruseTarlRovingRancher extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent(SubType.OX, "Oxen"); + + public BruseTarlRovingRancher(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}{W}"); + + 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); + + // Oxen you control have double strike. + this.addAbility(new SimpleStaticAbility( + new GainAbilityControlledEffect( + DoubleStrikeAbility.getInstance(), + Duration.WhileOnBattlefield, filter + ) + )); + + // Whenever Bruse Tarl, Roving Rancher enters the battlefield or attacks, exile the top card of your library. If it's a land card, create a 2/2 white Ox creature token. Otherwise, you may cast it until the end of your next turn. + this.addAbility(new EntersBattlefieldOrAttacksSourceTriggeredAbility(new BruseTarlRovingRancherEffect())); + } + + private BruseTarlRovingRancher(final BruseTarlRovingRancher card) { + super(card); + } + + @Override + public BruseTarlRovingRancher copy() { + return new BruseTarlRovingRancher(this); + } +} + +class BruseTarlRovingRancherEffect extends OneShotEffect { + + BruseTarlRovingRancherEffect() { + super(Outcome.Benefit); + staticText = "exile the top card of your library. " + + "If it's a land card, create a 2/2 white Ox creature token. " + + "Otherwise, you may cast it until the end of your next turn."; + } + + private BruseTarlRovingRancherEffect(final BruseTarlRovingRancherEffect effect) { + super(effect); + } + + @Override + public BruseTarlRovingRancherEffect copy() { + return new BruseTarlRovingRancherEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Library library = player.getLibrary(); + if (library == null || !library.hasCards()) { + return false; + } + Card card = library.getFromTop(game); + if (card == null) { + return false; + } + + if (card.isLand(game)) { + player.moveCards(card, Zone.EXILED, source, game); + new CreateTokenEffect(new Ox22Token()).apply(game, source); + } else { + PlayFromNotOwnHandZoneTargetEffect.exileAndPlayFromExile( + game, source, card, TargetController.YOU, + Duration.UntilEndOfYourNextTurn, + false, false, true + ); + } + return true; + } + +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/b/BubbleSmuggler.java b/Mage.Sets/src/mage/cards/b/BubbleSmuggler.java new file mode 100644 index 00000000000..7e305c24e69 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BubbleSmuggler.java @@ -0,0 +1,52 @@ +package mage.cards.b; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.AsTurnedFaceUpEffect; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.constants.SubType; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.keyword.DisguiseAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.counters.CounterType; + +/** + * + * @author notgreat + */ +public final class BubbleSmuggler extends CardImpl { + + public BubbleSmuggler(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}"); + + this.subtype.add(SubType.OCTOPUS); + this.subtype.add(SubType.FISH); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // Disguise {5}{U} + this.addAbility(new DisguiseAbility(this, new ManaCostsImpl<>("{5}{U}"))); + + // As Bubble Smuggler is turned face up, put four +1/+1 counters on it. + Effect effect = new AddCountersSourceEffect(CounterType.P1P1.createInstance(4)); + effect.setText("put four +1/+1 counters on it"); + Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new AsTurnedFaceUpEffect(effect, false)); + ability.setWorksFaceDown(true); + this.addAbility(ability); + } + + private BubbleSmuggler(final BubbleSmuggler card) { + super(card); + } + + @Override + public BubbleSmuggler copy() { + return new BubbleSmuggler(this); + } +} diff --git a/Mage.Sets/src/mage/cards/b/BucolicRanch.java b/Mage.Sets/src/mage/cards/b/BucolicRanch.java new file mode 100644 index 00000000000..19a944bdbb5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BucolicRanch.java @@ -0,0 +1,115 @@ +package mage.cards.b; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.mana.ColorlessManaAbility; +import mage.abilities.mana.ConditionalAnyColorManaAbility; +import mage.abilities.mana.conditional.ConditionalSpellManaBuilder; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.filter.FilterSpell; +import mage.game.Game; +import mage.players.Player; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class BucolicRanch extends CardImpl { + + private static final FilterSpell filter = new FilterSpell("a Mount spell"); + + static { + filter.add(SubType.MOUNT.getPredicate()); + } + + public BucolicRanch(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + this.subtype.add(SubType.DESERT); + + // {T}: Add {C}. + this.addAbility(new ColorlessManaAbility()); + + // {T}: Add one mana of any color. Spend this mana only to cast a Mount spell. + this.addAbility(new ConditionalAnyColorManaAbility( + new TapSourceCost(), 1, + new ConditionalSpellManaBuilder(filter), true + )); + + // {3}, {T}: Look at the top card of your library. If it's a Mount card, you may reveal it and put it into your hand. If you don't put it into your hand, you may put it on the bottom of your library. + Ability ability = new SimpleActivatedAbility(new BucolicRanchEffect(), new GenericManaCost(3)); + ability.addCost(new TapSourceCost()); + this.addAbility(ability); + } + + private BucolicRanch(final BucolicRanch card) { + super(card); + } + + @Override + public BucolicRanch copy() { + return new BucolicRanch(this); + } +} + +class BucolicRanchEffect extends OneShotEffect { + + private static final FilterCard filter = new FilterCard("a Mount card"); + + static { + filter.add(SubType.MOUNT.getPredicate()); + } + + BucolicRanchEffect() { + super(Outcome.Benefit); + staticText = "look at the top card of your library. If it's a Mount card, " + + "you may reveal it and put it into your hand. If you don't put it " + + "into your hand, you may put it on the bottom of your library"; + } + + private BucolicRanchEffect(final BucolicRanchEffect effect) { + super(effect); + } + + @Override + public BucolicRanchEffect copy() { + return new BucolicRanchEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Card card = player.getLibrary().getFromTop(game); + if (card == null) { + return false; + } + player.lookAtCards("Top card of library", card, game); + if (filter.match(card, player.getId(), source, game) && player.chooseUse( + outcome, "Put " + card.getName() + " into your hand?", source, game + )) { + player.revealCards(source, new CardsImpl(card), game); + player.moveCards(card, Zone.HAND, source, game); + } + if (Zone.LIBRARY.equals(game.getState().getZone(card.getId())) && player.chooseUse( + outcome, "Put " + card.getName() + " on the bottom of your library?", source, game + )) { + player.putCardsOnBottomOfLibrary(card, game, source, false); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/c/Cactarantula.java b/Mage.Sets/src/mage/cards/c/Cactarantula.java new file mode 100644 index 00000000000..4fd43a845db --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/Cactarantula.java @@ -0,0 +1,69 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.common.BecomesTargetSourceTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.cost.SpellCostReductionSourceEffect; +import mage.abilities.hint.ConditionHint; +import mage.abilities.hint.Hint; +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.Zone; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class Cactarantula extends CardImpl { + + private static final Condition condition = new PermanentsOnTheBattlefieldCondition( + new FilterPermanent(SubType.DESERT, "you control a Desert") + ); + + private static final Hint hint = new ConditionHint(condition); + + public Cactarantula(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G}{G}"); + + this.subtype.add(SubType.PLANT); + this.subtype.add(SubType.SPIDER); + this.power = new MageInt(6); + this.toughness = new MageInt(5); + + // This spell costs {1} less to cast if you control a Desert. + this.addAbility( + new SimpleStaticAbility(Zone.ALL, new SpellCostReductionSourceEffect(1, condition)) + .setRuleAtTheTop(true) + .addHint(hint) + ); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // Whenever Cactarantula becomes the target of a spell or ability an opponent controls, you may draw a card. + this.addAbility(new BecomesTargetSourceTriggeredAbility( + new DrawCardSourceControllerEffect(1), + StaticFilters.FILTER_SPELL_OR_ABILITY_OPPONENTS, + SetTargetPointer.NONE, true + )); + } + + private Cactarantula(final Cactarantula card) { + super(card); + } + + @Override + public Cactarantula copy() { + return new Cactarantula(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CactusPreserve.java b/Mage.Sets/src/mage/cards/c/CactusPreserve.java new file mode 100644 index 00000000000..351bec63b10 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CactusPreserve.java @@ -0,0 +1,79 @@ +package mage.cards.c; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTappedAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.common.CommanderGreatestManaValue; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.BecomesCreatureSourceEffect; +import mage.abilities.keyword.ReachAbility; +import mage.abilities.mana.AnyColorLandsProduceManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.game.permanent.token.custom.CreatureToken; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class CactusPreserve extends CardImpl { + + public CactusPreserve(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + this.subtype.add(SubType.DESERT); + + // Cactus Preserve enters the battlefield tapped. + this.addAbility(new EntersBattlefieldTappedAbility()); + + // {T}: Add one mana of any type that a land you control could produce. + this.addAbility(new AnyColorLandsProduceManaAbility(TargetController.YOU, false)); + + // {3}: Until end of turn, Cactus Preserve becomes an X/X green Plant creature with reach, where X is the greatest mana value among your commanders. It's still a land. + this.addAbility(new SimpleActivatedAbility(new CactusPreserveEffect(), new ManaCostsImpl<>("{3}"))); + } + + private CactusPreserve(final CactusPreserve card) { + super(card); + } + + @Override + public CactusPreserve copy() { + return new CactusPreserve(this); + } +} + +class CactusPreserveEffect extends OneShotEffect { + + CactusPreserveEffect() { + super(Outcome.BecomeCreature); + this.staticText = "Until end of turn, {this} becomes an X/X green Plant creature with reach, " + + "where X is the greatest mana value among your commanders. It's still a land."; + } + + private CactusPreserveEffect(final CactusPreserveEffect effect) { + super(effect); + } + + @Override + public CactusPreserveEffect copy() { + return new CactusPreserveEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + int xValue = CommanderGreatestManaValue.instance.calculate(game, source, this); + game.addEffect(new BecomesCreatureSourceEffect( + new CreatureToken(xValue, xValue, + "X/X green Plant creature with reach, where X is the greatest mana value among your commanders") + .withColor("G").withSubType(SubType.PLANT) + .withAbility(ReachAbility.getInstance()), + CardType.LAND, Duration.EndOfTurn), source + ); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/c/CactusfolkSureshot.java b/Mage.Sets/src/mage/cards/c/CactusfolkSureshot.java new file mode 100644 index 00000000000..943729d7513 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CactusfolkSureshot.java @@ -0,0 +1,64 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfCombatTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.ReachAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.abilities.keyword.WardAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.PowerPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CactusfolkSureshot extends CardImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent(); + + static { + filter.add(new PowerPredicate(ComparisonType.MORE_THAN, 3)); + } + + public CactusfolkSureshot(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}{G}"); + + this.subtype.add(SubType.PLANT); + this.subtype.add(SubType.MERCENARY); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // Ward {2} + this.addAbility(new WardAbility(new ManaCostsImpl<>("{2}"))); + + // At the beginning of combat on your turn, other creatures you control with power 4 or greater gain trample and haste until end of turn. + Ability ability = new BeginningOfCombatTriggeredAbility(new GainAbilityControlledEffect( + TrampleAbility.getInstance(), Duration.EndOfTurn, filter, true + ).setText("other creatures you control with power 4 or greater gain trample"), TargetController.YOU, false); + ability.addEffect(new GainAbilityControlledEffect( + HasteAbility.getInstance(), Duration.EndOfTurn, filter, true + ).setText("and haste until end of turn")); + this.addAbility(ability); + } + + private CactusfolkSureshot(final CactusfolkSureshot card) { + super(card); + } + + @Override + public CactusfolkSureshot copy() { + return new CactusfolkSureshot(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CalamityGallopingInferno.java b/Mage.Sets/src/mage/cards/c/CalamityGallopingInferno.java new file mode 100644 index 00000000000..33229316e6d --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CalamityGallopingInferno.java @@ -0,0 +1,109 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksWhileSaddledTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenCopyTargetEffect; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.SaddleAbility; +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.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.permanent.SaddledSourceThisTurnPredicate; +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 CalamityGallopingInferno extends CardImpl { + + public CalamityGallopingInferno(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{R}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HORSE); + this.subtype.add(SubType.MOUNT); + this.power = new MageInt(4); + this.toughness = new MageInt(6); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // Whenever Calamity, Galloping Inferno attacks while saddled, choose a nonlegendary creature that saddled it this turn and create a tapped and attacking token that's a copy of it. Sacrifice that token at the beginning of the next end step. Repeat this process once. + this.addAbility(new AttacksWhileSaddledTriggeredAbility(new CalamityGallopingInfernoEffect())); + + // Saddle 1 + this.addAbility(new SaddleAbility(1)); + } + + private CalamityGallopingInferno(final CalamityGallopingInferno card) { + super(card); + } + + @Override + public CalamityGallopingInferno copy() { + return new CalamityGallopingInferno(this); + } +} + +class CalamityGallopingInfernoEffect extends OneShotEffect { + + private static final FilterPermanent filter + = new FilterCreaturePermanent("nonlegendary creature that saddled it this turn"); + + static { + filter.add(Predicates.not(SuperType.LEGENDARY.getPredicate())); + filter.add(SaddledSourceThisTurnPredicate.instance); + } + + CalamityGallopingInfernoEffect() { + super(Outcome.Benefit); + staticText = "choose a nonlegendary creature that saddled it this turn " + + "and create a tapped and attacking token that's a copy of it. " + + "Sacrifice that token at the beginning of the next end step. Repeat this process once"; + } + + private CalamityGallopingInfernoEffect(final CalamityGallopingInfernoEffect effect) { + super(effect); + } + + @Override + public CalamityGallopingInfernoEffect copy() { + return new CalamityGallopingInfernoEffect(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; + } + for (int i = 0; i < 2; i++) { + TargetPermanent target = new TargetPermanent(filter); + target.withNotTarget(true); + player.choose(outcome, target, source, game); + Permanent permanent = game.getPermanent(target.getFirstTarget()); + if (permanent == null) { + continue; + } + CreateTokenCopyTargetEffect effect = new CreateTokenCopyTargetEffect( + null, null, false, 1, true, true + ); + effect.apply(game, source); + effect.sacrificeTokensCreatedAtNextEndStep(game, source); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/c/CallASurpriseWitness.java b/Mage.Sets/src/mage/cards/c/CallASurpriseWitness.java new file mode 100644 index 00000000000..f5ad039994c --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CallASurpriseWitness.java @@ -0,0 +1,51 @@ +package mage.cards.c; + +import java.util.UUID; + +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.abilities.effects.common.continuous.AddCreatureTypeAdditionEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.common.FilterCreatureCard; +import mage.filter.predicate.mageobject.ManaValuePredicate; +import mage.target.common.TargetCardInYourGraveyard; + +/** + * @author Cguy7777 + */ +public final class CallASurpriseWitness extends CardImpl { + + private static final FilterCreatureCard filter + = new FilterCreatureCard("creature card with mana value 3 or less from your graveyard"); + + static { + filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, 4)); + } + + public CallASurpriseWitness(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{W}"); + + // Return target creature card with mana value 3 or less from your graveyard to the battlefield. + // Put a flying counter on it. It's a Spirit in addition to its other types. + this.getSpellAbility().addEffect(new ReturnFromGraveyardToBattlefieldTargetEffect()); + this.getSpellAbility().addEffect(new AddCountersTargetEffect(CounterType.FLYING.createInstance()) + .setText("put a flying counter on it")); + this.getSpellAbility().addEffect(new AddCreatureTypeAdditionEffect(SubType.SPIRIT, false) + .setText("it's a Spirit in addition to its other types")); + this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(filter)); + } + + private CallASurpriseWitness(final CallASurpriseWitness card) { + super(card); + } + + @Override + public CallASurpriseWitness copy() { + return new CallASurpriseWitness(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CanyonCrab.java b/Mage.Sets/src/mage/cards/c/CanyonCrab.java new file mode 100644 index 00000000000..39cbb9e402e --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CanyonCrab.java @@ -0,0 +1,49 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.common.BeginningOfEndStepTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.condition.common.HaventCastSpellFromHandThisTurnCondition; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DrawDiscardControllerEffect; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class CanyonCrab extends CardImpl { + + public CanyonCrab(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}"); + + this.subtype.add(SubType.CRAB); + this.power = new MageInt(0); + this.toughness = new MageInt(5); + + // {1}{U}: Canyon Crab gets +2/-2 until end of turn. + this.addAbility(new SimpleActivatedAbility( + new BoostSourceEffect(2, -2, Duration.EndOfTurn), + new ManaCostsImpl<>("{1}{U}") + )); + + // At the beginning of your end step, if you haven't cast a spell from your hand this turn, draw a card, then discard a card. + this.addAbility(new BeginningOfEndStepTriggeredAbility( + Zone.BATTLEFIELD, new DrawDiscardControllerEffect(1, 1), + TargetController.YOU, HaventCastSpellFromHandThisTurnCondition.instance, false + ).addHint(HaventCastSpellFromHandThisTurnCondition.hint)); + } + + private CanyonCrab(final CanyonCrab card) { + super(card); + } + + @Override + public CanyonCrab copy() { + return new CanyonCrab(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CarpetOfFlowers.java b/Mage.Sets/src/mage/cards/c/CarpetOfFlowers.java index b723f54c576..897b8eb0801 100644 --- a/Mage.Sets/src/mage/cards/c/CarpetOfFlowers.java +++ b/Mage.Sets/src/mage/cards/c/CarpetOfFlowers.java @@ -75,7 +75,7 @@ class CarpetOfFlowersTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkInterveningIfClause(Game game) { - return !Boolean.TRUE.equals(game.getState().getValue(this.originalId.toString() + return !Boolean.TRUE.equals(game.getState().getValue(this.getOriginalId().toString() + "addMana" + game.getState().getZoneChangeCounter(sourceId))); } @@ -84,7 +84,7 @@ class CarpetOfFlowersTriggeredAbility extends TriggeredAbilityImpl { public boolean resolve(Game game) { boolean value = super.resolve(game); if (value == true) { - game.getState().setValue(this.originalId.toString() + game.getState().setValue(this.getOriginalId().toString() + "addMana" + game.getState().getZoneChangeCounter(sourceId), Boolean.TRUE); @@ -94,7 +94,7 @@ class CarpetOfFlowersTriggeredAbility extends TriggeredAbilityImpl { @Override public void reset(Game game) { - game.getState().setValue(this.originalId.toString() + game.getState().setValue(this.getOriginalId().toString() + "addMana" + game.getState().getZoneChangeCounter(sourceId), Boolean.FALSE); diff --git a/Mage.Sets/src/mage/cards/c/CaseFileAuditor.java b/Mage.Sets/src/mage/cards/c/CaseFileAuditor.java new file mode 100644 index 00000000000..2fb07a7ca9b --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CaseFileAuditor.java @@ -0,0 +1,131 @@ +package mage.cards.c; + +import java.util.UUID; +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.AsThoughManaEffect; +import mage.abilities.effects.common.LookLibraryAndPickControllerEffect; +import mage.abilities.meta.OrTriggeredAbility; +import mage.constants.AsThoughEffectType; +import mage.constants.Duration; +import mage.constants.ManaType; +import mage.constants.Outcome; +import mage.constants.PutCards; +import mage.constants.SubType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.filter.common.FilterEnchantmentCard; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.players.ManaPoolItem; +import mage.util.CardUtil; + +/** + * + * @author DominionSpy + */ +public final class CaseFileAuditor extends CardImpl { + + private static final FilterCard filter = new FilterEnchantmentCard(); + + public CaseFileAuditor(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.DETECTIVE); + this.power = new MageInt(1); + this.toughness = new MageInt(4); + + // When Case File Auditor enters the battlefield and whenever you solve a Case, look at the top six cards of your library. + // You may reveal an enchantment card from among them and put it into your hand. + // Put the rest on the bottom of your library in a random order. + this.addAbility(new OrTriggeredAbility(Zone.BATTLEFIELD, + new LookLibraryAndPickControllerEffect(6, 1, filter, PutCards.HAND, PutCards.BOTTOM_RANDOM), + false, "When {this} enters the battlefield and whenever you solve a Case, ", + new EntersBattlefieldTriggeredAbility(null), + new CaseFileAuditorTriggeredAbility())); + + // You may spend mana as though it were mana of any color to cast Case spells. + this.addAbility(new SimpleStaticAbility(new CaseFileAuditorManaEffect())); + } + + private CaseFileAuditor(final CaseFileAuditor card) { + super(card); + } + + @Override + public CaseFileAuditor copy() { + return new CaseFileAuditor(this); + } +} + +class CaseFileAuditorTriggeredAbility extends TriggeredAbilityImpl { + + CaseFileAuditorTriggeredAbility() { + super(Zone.BATTLEFIELD, null); + this.setTriggerPhrase("Whenever you solve a Case, "); + } + + private CaseFileAuditorTriggeredAbility(final CaseFileAuditorTriggeredAbility ability) { + super(ability); + } + + @Override + public CaseFileAuditorTriggeredAbility copy() { + return new CaseFileAuditorTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.CASE_SOLVED; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return event.getPlayerId().equals(this.getControllerId()); + } +} + +class CaseFileAuditorManaEffect extends AsThoughEffectImpl implements AsThoughManaEffect { + + CaseFileAuditorManaEffect() { + super(AsThoughEffectType.SPEND_OTHER_MANA, Duration.WhileOnBattlefield, Outcome.Benefit); + staticText = "you may spend mana as though it were mana of any color to cast Case spells"; + } + + private CaseFileAuditorManaEffect(final CaseFileAuditorManaEffect effect) { + super(effect); + } + + @Override + public CaseFileAuditorManaEffect copy() { + return new CaseFileAuditorManaEffect(this); + } + + @Override + public ManaType getAsThoughManaType(ManaType manaType, ManaPoolItem mana, UUID affectedControllerId, Ability source, Game game) { + return mana.getFirstAvailable(); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + if (!source.isControlledBy(affectedControllerId)) { + return false; + } + MageObject mageObject = game.getObject(CardUtil.getMainCardId(game, objectId)); + return mageObject != null && mageObject.hasSubtype(SubType.CASE, game); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/c/CaseOfTheLockedHothouse.java b/Mage.Sets/src/mage/cards/c/CaseOfTheLockedHothouse.java index 9cf8c65397c..bd852204977 100644 --- a/Mage.Sets/src/mage/cards/c/CaseOfTheLockedHothouse.java +++ b/Mage.Sets/src/mage/cards/c/CaseOfTheLockedHothouse.java @@ -12,7 +12,7 @@ import mage.abilities.decorator.ConditionalAsThoughEffect; import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; import mage.abilities.effects.common.continuous.PlayAdditionalLandsControllerEffect; -import mage.abilities.effects.common.continuous.PlayTheTopCardEffect; +import mage.abilities.effects.common.continuous.PlayFromTopOfLibraryEffect; import mage.abilities.hint.common.CaseSolvedHint; import mage.constants.ComparisonType; import mage.constants.Duration; @@ -64,7 +64,7 @@ public final class CaseOfTheLockedHothouse extends CardImpl { Ability solvedAbility = new SimpleStaticAbility(new ConditionalContinuousEffect( new LookAtTopCardOfLibraryAnyTimeEffect(), SolvedSourceCondition.SOLVED, "")); solvedAbility.addEffect(new ConditionalAsThoughEffect( - new PlayTheTopCardEffect(TargetController.YOU, filter2, false), + new PlayFromTopOfLibraryEffect(filter2), SolvedSourceCondition.SOLVED) .setText(", and you may play lands and cast creature and enchantment spells from the top of your library.")); diff --git a/Mage.Sets/src/mage/cards/c/CataclysmicProspecting.java b/Mage.Sets/src/mage/cards/c/CataclysmicProspecting.java new file mode 100644 index 00000000000..f0335fcfaa0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CataclysmicProspecting.java @@ -0,0 +1,151 @@ +package mage.cards.c; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.ManacostVariableValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DamageAllEffect; +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.WatcherScope; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ManaPaidEvent; +import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.token.TreasureToken; +import mage.util.Copyable; +import mage.watchers.Watcher; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * @author Susucr + */ +public final class CataclysmicProspecting extends CardImpl { + + private static final Hint hint = new ValueHint("Mana spent from a Desert", CataclysmicProspectingValue.instance); + + public CataclysmicProspecting(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{X}{R}{R}"); + + // Cataclysmic Prospecting deals X damage to each creature. For each mana from a Desert spent to cast this spell, create a tapped Treasure token. + this.getSpellAbility().addEffect(new DamageAllEffect(ManacostVariableValue.REGULAR, StaticFilters.FILTER_PERMANENT_CREATURE)); + this.getSpellAbility().addEffect(new CreateTokenEffect(new TreasureToken(), CataclysmicProspectingValue.instance, true, false) + .setText("For each mana from a Desert spent to cast this spell, create a tapped Treasure token.")); + this.getSpellAbility().addWatcher(new CataclysmicProspectingWatcher()); + this.getSpellAbility().addHint(hint); + } + + private CataclysmicProspecting(final CataclysmicProspecting card) { + super(card); + } + + @Override + public CataclysmicProspecting copy() { + return new CataclysmicProspecting(this); + } +} + +enum CataclysmicProspectingValue implements DynamicValue { + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return sourceAbility == null ? 0 : CataclysmicProspectingWatcher.getDesertsAmount(sourceAbility.getSourceId(), game); + } + + @Override + public CataclysmicProspectingValue copy() { + return this; + } + + @Override + public String toString() { + return "1"; + } + + @Override + public String getMessage() { + return "mana from a Desert spent to cast it"; + } + +} + +/** + * Inspired by {@link mage.watchers.common.ManaPaidSourceWatcher} + * If more cards like Cataclysmic care for mana produced by Deserts in the future, best to refactor the tracking there. + * For now the assumption is that it is a 1of, so don't want to track it in any game. + */ +class CataclysmicProspectingWatcher extends Watcher { + + private static final class DesertManaPaidTracker implements Serializable, Copyable { + private int desertMana = 0; + + private DesertManaPaidTracker() { + super(); + } + + private DesertManaPaidTracker(final DesertManaPaidTracker tracker) { + this.desertMana = tracker.desertMana; + } + + @Override + public DesertManaPaidTracker copy() { + return new DesertManaPaidTracker(this); + } + + private void increment(MageObject sourceObject, Game game) { + if (sourceObject != null && sourceObject.hasSubtype(SubType.DESERT, game)) { + desertMana++; + } + } + } + + private static final DesertManaPaidTracker emptyTracker = new DesertManaPaidTracker(); + private final Map manaMap = new HashMap<>(); + + public CataclysmicProspectingWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + switch (event.getType()) { + case ZONE_CHANGE: + if (((ZoneChangeEvent) event).getFromZone() == Zone.BATTLEFIELD + // Bug #9943 Memory Deluge cast from graveyard during the same turn + || ((ZoneChangeEvent) event).getToZone() == Zone.GRAVEYARD) { + manaMap.remove(event.getTargetId()); + } + return; + case MANA_PAID: + ManaPaidEvent manaEvent = (ManaPaidEvent) event; + manaMap.computeIfAbsent(manaEvent.getTargetId(), x -> new DesertManaPaidTracker()) + .increment(manaEvent.getSourceObject(), game); + manaMap.computeIfAbsent(manaEvent.getSourcePaidId(), x -> new DesertManaPaidTracker()) + .increment(manaEvent.getSourceObject(), game); + } + } + + @Override + public void reset() { + super.reset(); + manaMap.clear(); + } + + public static int getDesertsAmount(UUID sourceId, Game game) { + CataclysmicProspectingWatcher watcher = game.getState().getWatcher(CataclysmicProspectingWatcher.class); + return watcher == null ? 0 : watcher.manaMap.getOrDefault(sourceId, emptyTracker).desertMana; + } +} diff --git a/Mage.Sets/src/mage/cards/c/CathedralAcolyte.java b/Mage.Sets/src/mage/cards/c/CathedralAcolyte.java new file mode 100644 index 00000000000..47182f4df5d --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CathedralAcolyte.java @@ -0,0 +1,68 @@ +package mage.cards.c; + +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.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.keyword.WardAbility; +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.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.CounterAnyPredicate; +import mage.filter.predicate.permanent.EnteredThisTurnPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author notgreat + */ +public final class CathedralAcolyte extends CardImpl { + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(); + private static final FilterCreaturePermanent filter2 = new FilterCreaturePermanent("creature that entered the battlefield this turn"); + + static { + filter.add(CounterAnyPredicate.instance); + filter2.add(EnteredThisTurnPredicate.instance); + } + + public CathedralAcolyte(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.CLERIC); + this.power = new MageInt(1); + this.toughness = new MageInt(2); + + // Each creature you control with a counter on it has ward {1}. + this.addAbility(new SimpleStaticAbility( + new GainAbilityControlledEffect( + new WardAbility(new GenericManaCost(1)), Duration.WhileOnBattlefield, filter + ).setText("Each creature you control with a counter on it has ward {1}") + )); + + // {T}: Put a +1/+1 counter on target creature that entered the battlefield this turn. + Ability ability = new SimpleActivatedAbility( + new AddCountersTargetEffect(CounterType.P1P1.createInstance(1)), new TapSourceCost() + ); + ability.addTarget(new TargetPermanent(filter2)); + this.addAbility(ability); + } + + private CathedralAcolyte(final CathedralAcolyte card) { + super(card); + } + + @Override + public CathedralAcolyte copy() { + return new CathedralAcolyte(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CaughtInTheCrossfire.java b/Mage.Sets/src/mage/cards/c/CaughtInTheCrossfire.java new file mode 100644 index 00000000000..71dab2fa0f8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CaughtInTheCrossfire.java @@ -0,0 +1,53 @@ +package mage.cards.c; + +import mage.abilities.Mode; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.DamageAllEffect; +import mage.abilities.keyword.SpreeAbility; +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.Predicates; +import mage.filter.predicate.mageobject.OutlawPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CaughtInTheCrossfire extends CardImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent("outlaw creature"); + private static final FilterPermanent filter2 = new FilterCreaturePermanent("non-outlaw creature"); + + static { + filter.add(OutlawPredicate.instance); + filter.add(Predicates.not(OutlawPredicate.instance)); + } + + public CaughtInTheCrossfire(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{R}{R}"); + + // Spree + this.addAbility(new SpreeAbility(this)); + + // + {1} -- Caught in the Crossfire deals 2 damage to each outlaw creature. + this.getSpellAbility().addEffect(new DamageAllEffect(2, filter)); + this.getSpellAbility().withFirstModeCost(new GenericManaCost(1)); + + // + {1} -- Caught in the Crossfire deals 2 damage to each non-outlaw creature. + this.getSpellAbility().addMode(new Mode(new DamageAllEffect(2, filter2)) + .withCost(new GenericManaCost(1))); + } + + private CaughtInTheCrossfire(final CaughtInTheCrossfire card) { + super(card); + } + + @Override + public CaughtInTheCrossfire copy() { + return new CaughtInTheCrossfire(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CausticBronco.java b/Mage.Sets/src/mage/cards/c/CausticBronco.java new file mode 100644 index 00000000000..d8732ea5a9b --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CausticBronco.java @@ -0,0 +1,95 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.condition.common.SaddledCondition; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.SaddleAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.game.Game; +import mage.players.Player; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CausticBronco extends CardImpl { + + public CausticBronco(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}"); + + this.subtype.add(SubType.SNAKE); + this.subtype.add(SubType.HORSE); + this.subtype.add(SubType.MOUNT); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Whenever Caustic Bronco attacks, reveal the top card of your library and put it into your hand. You lose life equal to that card's mana value if Caustic Bronco isn't saddled. Otherwise, each opponent loses that much life. + this.addAbility(new AttacksTriggeredAbility(new CausticBroncoEffect())); + + // Saddle 3 + this.addAbility(new SaddleAbility(3)); + } + + private CausticBronco(final CausticBronco card) { + super(card); + } + + @Override + public CausticBronco copy() { + return new CausticBronco(this); + } +} + +class CausticBroncoEffect extends OneShotEffect { + + CausticBroncoEffect() { + super(Outcome.Benefit); + staticText = "reveal the top card of your library and put it into your hand. " + + "You lose life equal to that card's mana value if {this} isn't saddled. " + + "Otherwise, each opponent loses that much life"; + } + + private CausticBroncoEffect(final CausticBroncoEffect effect) { + super(effect); + } + + @Override + public CausticBroncoEffect copy() { + return new CausticBroncoEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Card card = player.getLibrary().getFromTop(game); + if (card == null) { + return false; + } + player.revealCards(source, new CardsImpl(card), game); + player.moveCards(card, Zone.HAND, source, game); + if (!SaddledCondition.instance.apply(game, source)) { + player.loseLife(card.getManaValue(), game, source, false); + return true; + } + for (UUID playerId : game.getOpponents(source.getControllerId())) { + Player opponent = game.getPlayer(playerId); + if (opponent != null) { + opponent.loseLife(card.getManaValue(), game, source, false); + } + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/c/CemeteryIlluminator.java b/Mage.Sets/src/mage/cards/c/CemeteryIlluminator.java index 72d499c61c3..0ee1e5920cb 100644 --- a/Mage.Sets/src/mage/cards/c/CemeteryIlluminator.java +++ b/Mage.Sets/src/mage/cards/c/CemeteryIlluminator.java @@ -1,32 +1,33 @@ package mage.cards.c; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; - import mage.MageIdentifier; import mage.MageInt; import mage.MageObject; import mage.MageObjectReference; import mage.abilities.Ability; +import mage.abilities.SpellAbility; import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.AsThoughEffectImpl; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; -import mage.cards.Card; -import mage.constants.*; import mage.abilities.keyword.FlyingAbility; +import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.constants.*; import mage.game.ExileZone; import mage.game.Game; -import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.common.TargetCardInGraveyard; import mage.util.CardUtil; -import mage.watchers.Watcher; +import mage.watchers.common.OnceEachTurnCastWatcher; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; /** * @@ -52,8 +53,9 @@ public final class CemeteryIlluminator extends CardImpl { // Once each turn, you may cast a spell from the top of your library if it shares a card type with a card exiled with Cemetery Illuminator. this.addAbility(new SimpleStaticAbility(new CemeteryIlluminatorPlayTopEffect()) - .setIdentifier(MageIdentifier.CemeteryIlluminatorWatcher), - new CemeteryIlluminatorWatcher()); + .setIdentifier(MageIdentifier.OnceEachTurnCastWatcher) + .addHint(OnceEachTurnCastWatcher.getHint()), + new OnceEachTurnCastWatcher()); } private CemeteryIlluminator(final CemeteryIlluminator card) { @@ -124,62 +126,46 @@ class CemeteryIlluminatorPlayTopEffect extends AsThoughEffectImpl { @Override public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { - // Same checks as in PlayTheTopCardEffect - // Once per turn clause checked by Watcher same as Lurrus of the Dream Den - if (affectedControllerId.equals(source.getControllerId())) { - Player controller = game.getPlayer(source.getControllerId()); - CemeteryIlluminatorWatcher watcher = game.getState().getWatcher(CemeteryIlluminatorWatcher.class); - Permanent sourceObject = game.getPermanent(source.getSourceId()); - if (controller != null && watcher != null && sourceObject != null && !watcher.isAbilityUsed(new MageObjectReference(sourceObject, game))) { - Card card = game.getCard(objectId); - Card topCard = controller.getLibrary().getFromTop(game); - if (card != null && topCard != null && topCard.getId().equals(card.getMainCard().getId()) && !card.isLand(game) && !card.getManaCost().isEmpty()) { - // Check if it shares a card type with exiled cards - UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), game.getState().getZoneChangeCounter(source.getSourceId())); - ExileZone exileZone = game.getExile().getExileZone(exileId); - if (exileZone != null) { - HashSet cardTypes = new HashSet<>(card.getCardType(game)); - for (UUID exileCardId : exileZone) { - Card exileCard = game.getCard(exileCardId); - if (exileCard != null) { - for (CardType exileType : exileCard.getCardType(game)) { - if (cardTypes.contains(exileType)) { - return true; - } - } - } - } - } - } - } - } - return false; - } -} - -class CemeteryIlluminatorWatcher extends Watcher { - - private final Set usedFrom = new HashSet<>(); - - public CemeteryIlluminatorWatcher() { - super(WatcherScope.GAME); + throw new IllegalArgumentException("Wrong code usage: can't call applies method on empty affectedAbility"); } @Override - public void watch(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.SPELL_CAST - && event.hasApprovingIdentifier(MageIdentifier.CemeteryIlluminatorWatcher)) { - usedFrom.add(event.getAdditionalReference().getApprovingMageObjectReference()); + public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) { + Player controller = game.getPlayer(source.getControllerId()); + OnceEachTurnCastWatcher watcher = game.getState().getWatcher(OnceEachTurnCastWatcher.class); + Permanent sourceObject = source.getSourcePermanentIfItStillExists(game); + if (controller == null || watcher == null || sourceObject == null) { + return false; } - } - - @Override - public void reset() { - super.reset(); - usedFrom.clear(); - } - - public boolean isAbilityUsed(MageObjectReference mor) { - return usedFrom.contains(mor); + // Reference logic from PlayFromTopOfLibraryEffect and CastFromGraveyardOnceEachTurnAbility + if (!playerId.equals(controller.getId()) || watcher.isAbilityUsed(playerId, new MageObjectReference(sourceObject, game))) { + return false; + } + Card card = game.getCard(objectId); + Card topCard = controller.getLibrary().getFromTop(game); + if (card == null || topCard == null || !topCard.getId().equals(card.getMainCard().getId())) { + return false; + } + if (!(affectedAbility instanceof SpellAbility)) { + return false; + } + // need to check characteristics of spell rather than card (e.g. adventure, morph, etc.) + Card cardToCast = ((SpellAbility) affectedAbility).getCharacteristics(game); + if (cardToCast.getManaCost().isEmpty()) { + return false; + } + Set cardTypes = new HashSet<>(cardToCast.getCardType(game)); + // Check if it shares a card type with exiled cards + UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), game.getState().getZoneChangeCounter(source.getSourceId())); + ExileZone exileZone = game.getExile().getExileZone(exileId); + if (exileZone == null) { + return false; + } + return exileZone + .stream() + .map(game::getCard) + .filter(Objects::nonNull) + .flatMap(c -> c.getCardType(game).stream()) + .anyMatch(cardTypes::contains); } } diff --git a/Mage.Sets/src/mage/cards/c/ChancellorOfTheSpires.java b/Mage.Sets/src/mage/cards/c/ChancellorOfTheSpires.java index b31654fa237..dbb4de9e61a 100644 --- a/Mage.Sets/src/mage/cards/c/ChancellorOfTheSpires.java +++ b/Mage.Sets/src/mage/cards/c/ChancellorOfTheSpires.java @@ -5,12 +5,13 @@ import mage.abilities.Ability; import mage.abilities.DelayedTriggeredAbility; import mage.abilities.common.ChancellorAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.effects.common.CastTargetForFreeEffect; +import mage.abilities.effects.common.MayCastTargetCardEffect; import mage.abilities.effects.common.MillCardsEachPlayerEffect; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.CastManaAdjustment; import mage.constants.SubType; import mage.constants.TargetController; import mage.filter.FilterCard; @@ -50,7 +51,7 @@ public final class ChancellorOfTheSpires extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // When Chancellor of the Spires enters the battlefield, you may cast target instant or sorcery card from an opponent's graveyard without paying its mana cost. - Ability ability = new EntersBattlefieldTriggeredAbility(new CastTargetForFreeEffect(), true); + Ability ability = new EntersBattlefieldTriggeredAbility(new MayCastTargetCardEffect(CastManaAdjustment.WITHOUT_PAYING_MANA_COST)); ability.addTarget(new TargetCardInOpponentsGraveyard(filter)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/c/ChandraAcolyteOfFlame.java b/Mage.Sets/src/mage/cards/c/ChandraAcolyteOfFlame.java index ff193396c61..96c88da3de7 100644 --- a/Mage.Sets/src/mage/cards/c/ChandraAcolyteOfFlame.java +++ b/Mage.Sets/src/mage/cards/c/ChandraAcolyteOfFlame.java @@ -8,7 +8,7 @@ import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.InfoEffect; -import mage.abilities.effects.common.MayCastTargetThenExileEffect; +import mage.abilities.effects.common.MayCastTargetCardEffect; import mage.abilities.effects.common.SacrificeTargetEffect; import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; import mage.abilities.effects.common.counter.AddCountersAllEffect; @@ -61,7 +61,7 @@ public final class ChandraAcolyteOfFlame extends CardImpl { this.addAbility(new LoyaltyAbility(new ChandraAcolyteOfFlameEffect(), 0)); // -2: You may cast target instant or sorcery card with converted mana cost 3 or less from your graveyard. If that card would be put into your graveyard this turn, exile it instead. - Ability ability = new LoyaltyAbility(new MayCastTargetThenExileEffect(false), -2); + Ability ability = new LoyaltyAbility(new MayCastTargetCardEffect(true), -2); ability.addTarget(new TargetCardInYourGraveyard(filter2)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/c/ChandraFlamesCatalyst.java b/Mage.Sets/src/mage/cards/c/ChandraFlamesCatalyst.java index 187f7aaa400..f747637bca2 100644 --- a/Mage.Sets/src/mage/cards/c/ChandraFlamesCatalyst.java +++ b/Mage.Sets/src/mage/cards/c/ChandraFlamesCatalyst.java @@ -6,7 +6,7 @@ import mage.abilities.LoyaltyAbility; import mage.abilities.effects.Effect; import mage.abilities.effects.common.DamagePlayersEffect; import mage.abilities.effects.common.DrawCardSourceControllerEffect; -import mage.abilities.effects.common.MayCastTargetThenExileEffect; +import mage.abilities.effects.common.MayCastTargetCardEffect; import mage.abilities.effects.common.continuous.CastFromHandWithoutPayingManaCostEffect; import mage.abilities.effects.common.discard.DiscardHandControllerEffect; import mage.cards.CardImpl; @@ -33,7 +33,7 @@ public final class ChandraFlamesCatalyst extends CardImpl { public ChandraFlamesCatalyst(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{4}{R}{R}"); - + this.supertype.add(SuperType.LEGENDARY); this.subtype.add(SubType.CHANDRA); this.setStartingLoyalty(5); @@ -42,7 +42,7 @@ public final class ChandraFlamesCatalyst extends CardImpl { this.addAbility(new LoyaltyAbility(new DamagePlayersEffect(3, TargetController.OPPONENT), 1)); // −2: You may cast target red instant or sorcery card from your graveyard. If that spell would be put into your graveyard this turn, exile it instead. - Ability ability = new LoyaltyAbility(new MayCastTargetThenExileEffect(false), -2); + Ability ability = new LoyaltyAbility(new MayCastTargetCardEffect(true), -2); ability.addTarget(new TargetCardInYourGraveyard(filter)); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/c/ChandrasSpitfire.java b/Mage.Sets/src/mage/cards/c/ChandrasSpitfire.java index aa70ccd5112..2d3800e8eb7 100644 --- a/Mage.Sets/src/mage/cards/c/ChandrasSpitfire.java +++ b/Mage.Sets/src/mage/cards/c/ChandrasSpitfire.java @@ -14,9 +14,8 @@ import mage.constants.SubType; import mage.constants.Duration; import mage.constants.Zone; import mage.game.Game; -import mage.game.events.DamagedPlayerEvent; +import mage.game.events.DamagedBatchForOnePlayerEvent; import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; /** * @@ -58,13 +57,13 @@ class ChandrasSpitfireAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PLAYER; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PLAYER; } @Override public boolean checkTrigger(GameEvent event, Game game) { - DamagedPlayerEvent damageEvent = (DamagedPlayerEvent)event; - return !damageEvent.isCombatDamage() && game.getOpponents(controllerId).contains(event.getTargetId()); + DamagedBatchForOnePlayerEvent dEvent = (DamagedBatchForOnePlayerEvent) event; + return !dEvent.isCombatDamage() && dEvent.getAmount() > 0 && game.getOpponents(controllerId).contains(dEvent.getTargetId()); } @Override diff --git a/Mage.Sets/src/mage/cards/c/CharredGraverobber.java b/Mage.Sets/src/mage/cards/c/CharredGraverobber.java new file mode 100644 index 00000000000..fd30e77025e --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CharredGraverobber.java @@ -0,0 +1,58 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.EscapesWithAbility; +import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect; +import mage.abilities.keyword.EscapeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.predicate.mageobject.OutlawPredicate; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CharredGraverobber extends CardImpl { + + private static final FilterCard filter = new FilterCard("outlaw card from your graveyard"); + + static { + filter.add(OutlawPredicate.instance); + } + + public CharredGraverobber(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); + + this.subtype.add(SubType.SKELETON); + this.subtype.add(SubType.MERCENARY); + this.power = new MageInt(3); + this.toughness = new MageInt(1); + + // When Charred Graverobber enters the battlefield, return target outlaw card from your graveyard to your hand. + Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnFromGraveyardToHandTargetEffect()); + ability.addTarget(new TargetCardInYourGraveyard(filter)); + this.addAbility(ability); + + // Escape---{3}{B}, Exile four other cards from your graveyard. + this.addAbility(new EscapeAbility(this, "{3}{B}", 4)); + + // Charred Graverobber escapes with a +1/+1 counter on it. + this.addAbility(new EscapesWithAbility(1)); + } + + private CharredGraverobber(final CharredGraverobber card) { + super(card); + } + + @Override + public CharredGraverobber copy() { + return new CharredGraverobber(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/ChitteringDoom.java b/Mage.Sets/src/mage/cards/c/ChitteringDoom.java index 8d6ca0e9cd7..1df1e22ab19 100644 --- a/Mage.Sets/src/mage/cards/c/ChitteringDoom.java +++ b/Mage.Sets/src/mage/cards/c/ChitteringDoom.java @@ -60,7 +60,7 @@ class ChitteringDoomTriggeredAbility extends TriggeredAbilityImpl { public boolean checkTrigger(GameEvent event, Game game) { DieRolledEvent drEvent = (DieRolledEvent) event; // silver border card must look for "result" instead "natural result" - return this.isControlledBy(event.getPlayerId()) && drEvent.getResult() >= 4; + return this.isControlledBy(event.getTargetId()) && drEvent.getResult() >= 4; } @Override diff --git a/Mage.Sets/src/mage/cards/c/ClaimJumper.java b/Mage.Sets/src/mage/cards/c/ClaimJumper.java new file mode 100644 index 00000000000..64e370cba94 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/ClaimJumper.java @@ -0,0 +1,76 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.common.OpponentControlsMoreCondition; +import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.StaticFilters; +import mage.target.common.TargetCardInLibrary; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class ClaimJumper extends CardImpl { + + private static final FilterCard filter = new FilterCard("Plains card"); + + static { + filter.add(SubType.PLAINS.getPredicate()); + } + + public ClaimJumper(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); + + this.subtype.add(SubType.RABBIT); + this.subtype.add(SubType.MERCENARY); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // When Claim Jumper enters the battlefield, if an opponent controls more lands than you, you may search your library for a Plains card and put it onto the battlefield tapped. Then if an opponent controls more lands than you, repeat this process once. If you search your library this way, shuffle. + Ability ability = new ConditionalInterveningIfTriggeredAbility( + new EntersBattlefieldTriggeredAbility( + new SearchLibraryPutInPlayEffect( + new TargetCardInLibrary(0, 1, filter), true + ), + true + ), + new OpponentControlsMoreCondition(StaticFilters.FILTER_LANDS), + "When {this} enters the battlefield, if an opponent controls more lands than you, " + + "you may search your library for a Plains card and put it onto the battlefield tapped. " + + "Then if an opponent controls more lands than you, repeat this process once. " + + "If you search your library this way, shuffle." + ); + ability.addEffect( + new ConditionalOneShotEffect( + new SearchLibraryPutInPlayEffect( + new TargetCardInLibrary(0, 1, filter), true, false, true + ), + new OpponentControlsMoreCondition(StaticFilters.FILTER_LANDS) + ) + ); + this.addAbility(ability); + } + + private ClaimJumper(final ClaimJumper card) { + super(card); + } + + @Override + public ClaimJumper copy() { + return new ClaimJumper(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CollectorsCage.java b/Mage.Sets/src/mage/cards/c/CollectorsCage.java new file mode 100644 index 00000000000..76e2dd62285 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CollectorsCage.java @@ -0,0 +1,53 @@ +package mage.cards.c; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.condition.common.CovenCondition; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.HideawayPlayEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.hint.common.CovenHint; +import mage.abilities.keyword.HideawayAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.counters.CounterType; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CollectorsCage extends CardImpl { + + public CollectorsCage(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{W}"); + + // Hideaway 5 + this.addAbility(new HideawayAbility(5)); + + // {1}, {T}: Put a +1/+1 counter on target creature you control. Then if you control three or more creatures with different powers, you may play the exiled card without paying its mana cost. + Ability ability = new SimpleActivatedAbility( + new AddCountersTargetEffect(CounterType.P1P1.createInstance()), new GenericManaCost(1) + ); + ability.addCost(new TapSourceCost()); + ability.addEffect(new ConditionalOneShotEffect( + new HideawayPlayEffect(), CovenCondition.instance, "Then if you control three or more " + + "creatures with different powers, you may play the exiled card without paying its mana cost" + )); + ability.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(ability.addHint(CovenHint.instance)); + } + + private CollectorsCage(final CollectorsCage card) { + super(card); + } + + @Override + public CollectorsCage copy() { + return new CollectorsCage(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/ColossalRattlewurm.java b/Mage.Sets/src/mage/cards/c/ColossalRattlewurm.java new file mode 100644 index 00000000000..7cbbc6882da --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/ColossalRattlewurm.java @@ -0,0 +1,80 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.costs.common.ExileSourceFromGraveCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; +import mage.abilities.hint.ConditionHint; +import mage.abilities.hint.Hint; +import mage.abilities.keyword.FlashAbility; +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.Zone; +import mage.filter.FilterCard; +import mage.filter.common.FilterControlledPermanent; +import mage.target.common.TargetCardInLibrary; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ColossalRattlewurm extends CardImpl { + + private static final Condition condition + = new PermanentsOnTheBattlefieldCondition(new FilterControlledPermanent(SubType.DESERT)); + private static final Hint hint = new ConditionHint(condition, "You control a Desert"); + private static final FilterCard filter = new FilterCard("a Desert card"); + + static { + filter.add(SubType.DESERT.getPredicate()); + } + + public ColossalRattlewurm(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}{G}"); + + this.subtype.add(SubType.WURM); + this.power = new MageInt(6); + this.toughness = new MageInt(5); + + // Colossal Rattlewurm has flash as long as you control a Desert. + this.addAbility(new SimpleStaticAbility( + Zone.ALL, + new ConditionalContinuousEffect(new GainAbilitySourceEffect( + FlashAbility.getInstance(), Duration.WhileOnBattlefield, true + ), condition, "{this} has flash as long as you control a Desert") + ).setRuleAtTheTop(true).addHint(hint)); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // {1}{G}, Exile Colossal Rattlewurm from your graveyard: Search your library for a Desert card, put it onto the battlefield tapped, then shuffle. + Ability ability = new SimpleActivatedAbility( + Zone.GRAVEYARD, + new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(filter), true), + new ManaCostsImpl<>("{1}{G}") + ); + ability.addCost(new ExileSourceFromGraveCost()); + this.addAbility(ability); + } + + private ColossalRattlewurm(final ColossalRattlewurm card) { + super(card); + } + + @Override + public ColossalRattlewurm copy() { + return new ColossalRattlewurm(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/ConcealedWeapon.java b/Mage.Sets/src/mage/cards/c/ConcealedWeapon.java index 878bb1dbf47..8fa005e6898 100644 --- a/Mage.Sets/src/mage/cards/c/ConcealedWeapon.java +++ b/Mage.Sets/src/mage/cards/c/ConcealedWeapon.java @@ -41,7 +41,7 @@ public final class ConcealedWeapon extends CardImpl { this.addAbility(ability); // Equip {1}{R} - this.addAbility(new EquipAbility(Outcome.BoostCreature, new ManaCostsImpl<>("{1}{R}"))); + this.addAbility(new EquipAbility(Outcome.BoostCreature, new ManaCostsImpl<>("{1}{R}"), false)); } private ConcealedWeapon(final ConcealedWeapon card) { diff --git a/Mage.Sets/src/mage/cards/c/ConduitOfWorlds.java b/Mage.Sets/src/mage/cards/c/ConduitOfWorlds.java index cddd85e63d0..9f2c649eaf0 100644 --- a/Mage.Sets/src/mage/cards/c/ConduitOfWorlds.java +++ b/Mage.Sets/src/mage/cards/c/ConduitOfWorlds.java @@ -4,6 +4,7 @@ import mage.ApprovingObject; import mage.abilities.Ability; import mage.abilities.common.ActivateAsSorceryActivatedAbility; import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.HaventCastSpellThisTurnCondition; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; import mage.abilities.effects.OneShotEffect; @@ -21,7 +22,6 @@ import mage.game.Game; import mage.game.events.GameEvent; import mage.players.Player; import mage.target.common.TargetCardInYourGraveyard; -import mage.watchers.common.SpellsCastWatcher; import java.util.UUID; @@ -78,10 +78,7 @@ class ConduitOfWorldsEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - if (!game.getState() - .getWatcher(SpellsCastWatcher.class) - .getSpellsCastThisTurn(source.getControllerId()) - .isEmpty()) { + if (!HaventCastSpellThisTurnCondition.instance.apply(game, source)) { return false; } Player player = game.getPlayer(source.getControllerId()); diff --git a/Mage.Sets/src/mage/cards/c/ConduitPylons.java b/Mage.Sets/src/mage/cards/c/ConduitPylons.java new file mode 100644 index 00000000000..abedb87ee8d --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/ConduitPylons.java @@ -0,0 +1,47 @@ +package mage.cards.c; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.keyword.SurveilEffect; +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 ConduitPylons extends CardImpl { + + public ConduitPylons(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + this.subtype.add(SubType.DESERT); + + // When Conduit Pylons enters the battlefield, surveil 1. + this.addAbility(new EntersBattlefieldTriggeredAbility(new SurveilEffect(1))); + + // {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); + } + + private ConduitPylons(final ConduitPylons card) { + super(card); + } + + @Override + public ConduitPylons copy() { + return new ConduitPylons(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CongregationGryff.java b/Mage.Sets/src/mage/cards/c/CongregationGryff.java new file mode 100644 index 00000000000..9632bcfdcbc --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CongregationGryff.java @@ -0,0 +1,62 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.common.AttacksWhileSaddledTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.LifelinkAbility; +import mage.abilities.keyword.SaddleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.filter.common.FilterControlledPermanent; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class CongregationGryff extends CardImpl { + + private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(new FilterControlledPermanent(SubType.MOUNT)); + private static final Hint hint = new ValueHint("Number of Mounts you control", xValue); + + public CongregationGryff(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}{W}"); + + this.subtype.add(SubType.HIPPOGRIFF); + this.subtype.add(SubType.MOUNT); + this.power = new MageInt(1); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // Whenever Congregation Gryff attacks while saddled, it gets +X/+X until end of turn, where X is the number of Mounts you control. + this.addAbility(new AttacksWhileSaddledTriggeredAbility( + new BoostSourceEffect(xValue, xValue, Duration.EndOfTurn) + .setText("it gets +X/+X until end of turn, where X is the number of Mounts you control") + ).addHint(hint)); + + // Saddle 3 + this.addAbility(new SaddleAbility(3)); + } + + private CongregationGryff(final CongregationGryff card) { + super(card); + } + + @Override + public CongregationGryff copy() { + return new CongregationGryff(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/ConspicuousSnoop.java b/Mage.Sets/src/mage/cards/c/ConspicuousSnoop.java index a790c80537f..fd371f10dab 100644 --- a/Mage.Sets/src/mage/cards/c/ConspicuousSnoop.java +++ b/Mage.Sets/src/mage/cards/c/ConspicuousSnoop.java @@ -3,7 +3,7 @@ package mage.cards.c; import mage.MageInt; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.GainActivatedAbilitiesOfTopCardEffect; -import mage.abilities.effects.common.continuous.PlayTheTopCardEffect; +import mage.abilities.effects.common.continuous.PlayFromTopOfLibraryEffect; import mage.abilities.effects.common.continuous.PlayWithTheTopCardRevealedEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -37,7 +37,7 @@ public final class ConspicuousSnoop extends CardImpl { this.addAbility(new SimpleStaticAbility(new PlayWithTheTopCardRevealedEffect())); // You may cast Goblin spells from the top of your library. - this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(TargetController.YOU, filter, false))); + this.addAbility(new SimpleStaticAbility(new PlayFromTopOfLibraryEffect(filter))); // As long as the top card of your library is a Goblin card, Conspicuous Snoop has all activated abilities of that card. this.addAbility(new SimpleStaticAbility(new GainActivatedAbilitiesOfTopCardEffect(filter.copy().withMessage("a Goblin card")))); diff --git a/Mage.Sets/src/mage/cards/c/ConspiracyUnraveler.java b/Mage.Sets/src/mage/cards/c/ConspiracyUnraveler.java new file mode 100644 index 00000000000..0804a14dfba --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/ConspiracyUnraveler.java @@ -0,0 +1,94 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.SourceIsSpellCondition; +import mage.abilities.costs.AlternativeCostSourceAbility; +import mage.abilities.costs.common.CollectEvidenceCost; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.players.Player; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class ConspiracyUnraveler extends CardImpl { + + public ConspiracyUnraveler(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{U}{U}"); + + this.subtype.add(SubType.SPHINX); + this.subtype.add(SubType.DETECTIVE); + this.power = new MageInt(6); + this.toughness = new MageInt(6); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // You may collect evidence 10 rather than pay the mana cost for spells that you cast. + this.addAbility(new SimpleStaticAbility(new ConspiracyUnravelerInsteadEffect())); + } + + private ConspiracyUnraveler(final ConspiracyUnraveler card) { + super(card); + } + + @Override + public ConspiracyUnraveler copy() { + return new ConspiracyUnraveler(this); + } +} + +// Inspired by WUBRGInsteadEffect +class ConspiracyUnravelerInsteadEffect extends ContinuousEffectImpl { + + private final AlternativeCostSourceAbility alternativeCastingCostAbility = new AlternativeCostSourceAbility(new CollectEvidenceCost(10), SourceIsSpellCondition.instance); + + ConspiracyUnravelerInsteadEffect() { + super(Duration.WhileOnBattlefield, Outcome.Detriment); + staticText = "You may collect evidence 10 rather than pay the mana cost for spells that you cast"; + } + + protected ConspiracyUnravelerInsteadEffect(final ConspiracyUnravelerInsteadEffect effect) { + super(effect); + } + + @Override + public ConspiracyUnravelerInsteadEffect copy() { + return new ConspiracyUnravelerInsteadEffect(this); + } + + @Override + public void init(Ability source, Game game, UUID activePlayerId) { + super.init(source, game, activePlayerId); + alternativeCastingCostAbility.setSourceId(source.getSourceId()); + } + + @Override + public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + controller.getAlternativeSourceCosts().add(alternativeCastingCostAbility); + return true; + } + return false; + } + + @Override + public boolean apply(Game game, Ability source) { + return false; + } + + @Override + public boolean hasLayer(Layer layer) { + return layer == Layer.RulesEffects; + } + +} diff --git a/Mage.Sets/src/mage/cards/c/ConsumingAshes.java b/Mage.Sets/src/mage/cards/c/ConsumingAshes.java new file mode 100644 index 00000000000..2bed41c422f --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/ConsumingAshes.java @@ -0,0 +1,69 @@ +package mage.cards.c; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class ConsumingAshes extends CardImpl { + + public ConsumingAshes(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{B}{B}"); + + // Exile target creature. If it had mana value 3 or less, surveil 2. + this.getSpellAbility().addEffect(new ConsumingAshesEffect()); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + } + + private ConsumingAshes(final ConsumingAshes card) { + super(card); + } + + @Override + public ConsumingAshes copy() { + return new ConsumingAshes(this); + } +} + +class ConsumingAshesEffect extends OneShotEffect { + + ConsumingAshesEffect() { + super(Outcome.Exile); + staticText = "Exile target creature. If it had mana value 3 or less, surveil 2."; + } + + private ConsumingAshesEffect(final ConsumingAshesEffect effect) { + super(effect); + } + + @Override + public ConsumingAshesEffect copy() { + return new ConsumingAshesEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Permanent permanent = game.getPermanent(source.getFirstTarget()); + if (controller == null || permanent == null) { + return false; + } + permanent.moveToExile(null, "", source, game); + if (permanent.getManaValue() <= 3) { + controller.surveil(2, source, game); + } + return true; + } + +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/c/ContestedGameBall.java b/Mage.Sets/src/mage/cards/c/ContestedGameBall.java index 9f1f5ea487b..c9493559c85 100644 --- a/Mage.Sets/src/mage/cards/c/ContestedGameBall.java +++ b/Mage.Sets/src/mage/cards/c/ContestedGameBall.java @@ -22,7 +22,7 @@ import mage.constants.Outcome; import mage.constants.Zone; import mage.counters.CounterType; import mage.game.Game; -import mage.game.events.DamagedBatchEvent; +import mage.game.events.DamagedBatchForOnePlayerEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.permanent.token.TreasureToken; @@ -84,7 +84,7 @@ class ContestedGameBallTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { - if (((DamagedBatchEvent) event).isCombatDamage() && event.getPlayerId().equals(this.getControllerId())) { + if (((DamagedBatchForOnePlayerEvent) event).isCombatDamage() && event.getTargetId().equals(this.getControllerId())) { this.getAllEffects().setTargetPointer(new FixedTarget(game.getActivePlayerId())); // attacking player is active player return true; diff --git a/Mage.Sets/src/mage/cards/c/CorporealProjection.java b/Mage.Sets/src/mage/cards/c/CorporealProjection.java new file mode 100644 index 00000000000..e91c44cbe31 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CorporealProjection.java @@ -0,0 +1,47 @@ +package mage.cards.c; + +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.continuous.GainAbilityAllEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.MyriadAbility; +import mage.abilities.keyword.OverloadAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.filter.StaticFilters; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CorporealProjection extends CardImpl { + + public CorporealProjection(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{U}{R}"); + + // Target creature you control gains myriad until end of turn. + this.getSpellAbility().addEffect(new GainAbilityTargetEffect(new MyriadAbility(false))); + this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent()); + + // Overload {3}{U}{U}{R}{R} + this.addAbility(new OverloadAbility( + this, + new GainAbilityAllEffect( + new MyriadAbility(false), Duration.EndOfTurn, + StaticFilters.FILTER_CONTROLLED_CREATURE + ), new ManaCostsImpl<>("{3}{U}{U}{R}{R}") + )); + } + + private CorporealProjection(final CorporealProjection card) { + super(card); + } + + @Override + public CorporealProjection copy() { + return new CorporealProjection(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CourserOfKruphix.java b/Mage.Sets/src/mage/cards/c/CourserOfKruphix.java index 9ed69c46a19..d782911fcbb 100644 --- a/Mage.Sets/src/mage/cards/c/CourserOfKruphix.java +++ b/Mage.Sets/src/mage/cards/c/CourserOfKruphix.java @@ -4,7 +4,7 @@ import mage.MageInt; import mage.abilities.common.LandfallAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.GainLifeEffect; -import mage.abilities.effects.common.continuous.PlayTheTopCardEffect; +import mage.abilities.effects.common.continuous.PlayFromTopOfLibraryEffect; import mage.abilities.effects.common.continuous.PlayWithTheTopCardRevealedEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -34,7 +34,7 @@ public final class CourserOfKruphix extends CardImpl { this.addAbility(new SimpleStaticAbility(new PlayWithTheTopCardRevealedEffect())); // You may play lands from the top of your library. - this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(TargetController.YOU, filter, false))); + this.addAbility(new SimpleStaticAbility(new PlayFromTopOfLibraryEffect(filter))); // Whenever a land enters the battlefield under your control, you gain 1 life. this.addAbility(new LandfallAbility(new GainLifeEffect(1))); diff --git a/Mage.Sets/src/mage/cards/c/CourtOfLocthwain.java b/Mage.Sets/src/mage/cards/c/CourtOfLocthwain.java index e3d9bcf489f..0793c17445a 100644 --- a/Mage.Sets/src/mage/cards/c/CourtOfLocthwain.java +++ b/Mage.Sets/src/mage/cards/c/CourtOfLocthwain.java @@ -89,7 +89,7 @@ class CourtOfLocthwainFirstEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); - if (controller == null || opponent == null || source == null) { + if (controller == null || opponent == null) { return false; } Card card = opponent.getLibrary().getFromTop(game); @@ -195,8 +195,10 @@ class CourtOfLocthwainCastForFreeEffect extends AsThoughEffectImpl { UUID exileId = CourtOfLocthwain.getExileZoneId(mor, game); ExileZone exileZone = game.getExile().getExileZone(exileId); + + Card card = game.getCard(objectId); // Is the card attempted to be played in the ExiledZone? - if (exileZone == null || !exileZone.contains(objectId)) { + if (exileZone == null || card == null || !exileZone.contains(card.getMainCard().getId())) { return false; } // can this ability still be used this turn? @@ -220,12 +222,11 @@ class CourtOfLocthwainWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { - UUID playerId = event.getPlayerId(); if (event.getType() == GameEvent.EventType.SPELL_CAST && event.hasApprovingIdentifier(MageIdentifier.CourtOfLocthwainWatcher) - && playerId != null) { + && event.getPlayerId() != null) { decrementCastAvailable( - playerId, + event.getPlayerId(), event.getAdditionalReference().getApprovingMageObjectReference() ); } diff --git a/Mage.Sets/src/mage/cards/c/CracklingSpellslinger.java b/Mage.Sets/src/mage/cards/c/CracklingSpellslinger.java new file mode 100644 index 00000000000..786bbb34a5c --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CracklingSpellslinger.java @@ -0,0 +1,54 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.common.CastFromEverywhereSourceCondition; +import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; +import mage.abilities.effects.common.continuous.NextSpellCastHasAbilityEffect; +import mage.abilities.keyword.FlashAbility; +import mage.abilities.keyword.StormAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.common.FilterInstantOrSorceryCard; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class CracklingSpellslinger extends CardImpl { + + private static final FilterCard filter = new FilterInstantOrSorceryCard("instant or sorcery spell"); + + public CracklingSpellslinger(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}{R}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // When Crackling Spellslinger enters the battlefield, if you cast it, the next instant or sorcery spell you cast this turn has storm. + this.addAbility(new ConditionalInterveningIfTriggeredAbility( + new EntersBattlefieldTriggeredAbility(new NextSpellCastHasAbilityEffect(new StormAbility(), filter)), + CastFromEverywhereSourceCondition.instance, + "When {this} enters the battlefield, if you cast it, " + + "the next instant or sorcery spell you cast this turn has storm" + )); + } + + private CracklingSpellslinger(final CracklingSpellslinger card) { + super(card); + } + + @Override + public CracklingSpellslinger copy() { + return new CracklingSpellslinger(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CranialExtraction.java b/Mage.Sets/src/mage/cards/c/CranialExtraction.java index c3364ddd381..71b12f9da90 100644 --- a/Mage.Sets/src/mage/cards/c/CranialExtraction.java +++ b/Mage.Sets/src/mage/cards/c/CranialExtraction.java @@ -1,7 +1,6 @@ package mage.cards.c; import mage.abilities.Ability; -import mage.abilities.Mode; import mage.abilities.effects.common.ChooseACardNameEffect; import mage.abilities.effects.common.search.SearchTargetGraveyardHandLibraryForCardNameAndExileEffect; import mage.cards.CardImpl; @@ -53,11 +52,12 @@ class CranialExtractionEffect extends SearchTargetGraveyardHandLibraryForCardNam @Override public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); Player player = game.getPlayer(this.getTargetPointer().getFirst(game, source)); - if (player == null) { + if (controller == null || player == null) { return true; } - String cardName = ChooseACardNameEffect.TypeOfName.NON_LAND_NAME.getChoice(player, game, source, false); + String cardName = ChooseACardNameEffect.TypeOfName.NON_LAND_NAME.getChoice(controller, game, source, false); if (cardName == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/c/CreosoteHeath.java b/Mage.Sets/src/mage/cards/c/CreosoteHeath.java new file mode 100644 index 00000000000..9d1b6d12750 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CreosoteHeath.java @@ -0,0 +1,48 @@ +package mage.cards.c; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTappedAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.mana.GreenManaAbility; +import mage.abilities.mana.WhiteManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CreosoteHeath extends CardImpl { + + public CreosoteHeath(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + this.subtype.add(SubType.DESERT); + + // Creosote Heath enters the battlefield tapped. + this.addAbility(new EntersBattlefieldTappedAbility()); + + // When Creosote Heath enters the battlefield, it deals 1 damage to target opponent. + Ability ability = new EntersBattlefieldTriggeredAbility(new DamageTargetEffect(1, "it")); + ability.addTarget(new TargetOpponent()); + this.addAbility(ability); + + // {T}: Add {G} or {W}. + this.addAbility(new GreenManaAbility()); + this.addAbility(new WhiteManaAbility()); + } + + private CreosoteHeath(final CreosoteHeath card) { + super(card); + } + + @Override + public CreosoteHeath copy() { + return new CreosoteHeath(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CriticalHit.java b/Mage.Sets/src/mage/cards/c/CriticalHit.java index eb9f4cce203..d7be543f5d1 100644 --- a/Mage.Sets/src/mage/cards/c/CriticalHit.java +++ b/Mage.Sets/src/mage/cards/c/CriticalHit.java @@ -62,7 +62,7 @@ class CriticalHitTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { DieRolledEvent drEvent = (DieRolledEvent) event; - return isControlledBy(event.getPlayerId()) + return isControlledBy(event.getTargetId()) && drEvent.getNaturalResult() == 20; } diff --git a/Mage.Sets/src/mage/cards/c/CrowdControlWarden.java b/Mage.Sets/src/mage/cards/c/CrowdControlWarden.java new file mode 100644 index 00000000000..aceb8da7864 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CrowdControlWarden.java @@ -0,0 +1,116 @@ +package mage.cards.c; + +import java.util.UUID; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.constants.*; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.keyword.DisguiseAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.events.EntersTheBattlefieldEvent; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; + +/** + * @author Cguy7777 + */ +public final class CrowdControlWarden extends CardImpl { + + private static final Hint hint = new ValueHint( + "Other creatures you control", + new PermanentsOnBattlefieldCount(StaticFilters.FILTER_CONTROLLED_ANOTHER_CREATURE)); + + public CrowdControlWarden(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}{W}"); + + this.subtype.add(SubType.CENTAUR); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // As Crowd-Control Warden enters the battlefield or is turned face up, put X +1/+1 counters on it, where X is the number of other creatures you control. + Ability ability = new SimpleStaticAbility(Zone.ALL, new CrowdControlWardenReplacementEffect()); + ability.setWorksFaceDown(true); + ability.addHint(hint); + this.addAbility(ability); + + // Disguise {3}{G/W}{G/W} + this.addAbility(new DisguiseAbility(this, new ManaCostsImpl<>("{3}{G/W}{G/W}"))); + } + + private CrowdControlWarden(final CrowdControlWarden card) { + super(card); + } + + @Override + public CrowdControlWarden copy() { + return new CrowdControlWarden(this); + } +} + +class CrowdControlWardenReplacementEffect extends ReplacementEffectImpl { + + private static final DynamicValue xValue + = new PermanentsOnBattlefieldCount(StaticFilters.FILTER_CONTROLLED_ANOTHER_CREATURE); + + CrowdControlWardenReplacementEffect() { + super(Duration.WhileOnBattlefield, Outcome.BoostCreature); + staticText = "as {this} enters the battlefield or is turned face up, " + + "put X +1/+1 counters on it, where X is the number of other creatures you control"; + } + + private CrowdControlWardenReplacementEffect(final CrowdControlWardenReplacementEffect effect) { + super(effect); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + switch (event.getType()) { + case ENTERS_THE_BATTLEFIELD: + case TURN_FACE_UP: + return true; + default: + return false; + } + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + if (event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD) { + if (event.getTargetId().equals(source.getSourceId())) { + Permanent sourcePermanent = ((EntersTheBattlefieldEvent) event).getTarget(); + if (sourcePermanent != null && !sourcePermanent.isFaceDown(game)) { + return true; + } + } + } + if (event.getType() == GameEvent.EventType.TURN_FACE_UP) { + return event.getTargetId().equals(source.getSourceId()); + } + return false; + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + new AddCountersSourceEffect(CounterType.P1P1.createInstance(0), xValue, false) + .apply(game, source); + return false; + } + + @Override + public CrowdControlWardenReplacementEffect copy() { + return new CrowdControlWardenReplacementEffect(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/CunningCoyote.java b/Mage.Sets/src/mage/cards/c/CunningCoyote.java new file mode 100644 index 00000000000..97e1d5fc200 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CunningCoyote.java @@ -0,0 +1,54 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.PlotAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CunningCoyote extends CardImpl { + + public CunningCoyote(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}"); + + this.subtype.add(SubType.COYOTE); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // When Cunning Coyote enters the battlefield, another target creature you control gets +1/+1 and gains haste until end of turn. + Ability ability = new EntersBattlefieldTriggeredAbility(new BoostTargetEffect(1, 1) + .setText("another target creature you control gets +1/+1")); + ability.addEffect(new GainAbilityTargetEffect(HasteAbility.getInstance()) + .setText("and gains haste until end of turn")); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_ANOTHER_TARGET_CREATURE_YOU_CONTROL)); + this.addAbility(ability); + + // Plot {1}{R} + this.addAbility(new PlotAbility("{1}{R}")); + } + + private CunningCoyote(final CunningCoyote card) { + super(card); + } + + @Override + public CunningCoyote copy() { + return new CunningCoyote(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DanceOfTheTumbleweeds.java b/Mage.Sets/src/mage/cards/d/DanceOfTheTumbleweeds.java new file mode 100644 index 00000000000..9a158a24685 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DanceOfTheTumbleweeds.java @@ -0,0 +1,89 @@ +package mage.cards.d; + +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.dynamicvalue.common.LandsYouControlCount; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; +import mage.abilities.hint.common.LandsYouControlHint; +import mage.abilities.keyword.SpreeAbility; +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.Predicates; +import mage.game.Game; +import mage.game.permanent.token.SeedGuardianToken; +import mage.target.common.TargetCardInLibrary; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DanceOfTheTumbleweeds extends CardImpl { + + private static final FilterCard filter = new FilterCard("basic land card or a Desert card"); + + static { + filter.add(Predicates.or( + Predicates.and( + SuperType.BASIC.getPredicate(), + CardType.LAND.getPredicate() + ), SubType.DESERT.getPredicate() + )); + } + + public DanceOfTheTumbleweeds(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{G}"); + + // Spree + this.addAbility(new SpreeAbility(this)); + + // + {1} -- Search your library for a basic land card or a Desert card, put it onto the battlefield, then shuffle. + this.getSpellAbility().addEffect(new SearchLibraryPutInPlayEffect( + new TargetCardInLibrary(filter) + )); + this.getSpellAbility().withFirstModeCost(new GenericManaCost(1)); + + // + {3} -- Create an X/X green Elemental creature token, where X is the number of lands you control. + this.getSpellAbility().addMode(new Mode(new DanceOfTheTumbleweedsEffect()).withCost(new GenericManaCost(3))); + this.getSpellAbility().addHint(LandsYouControlHint.instance); + } + + private DanceOfTheTumbleweeds(final DanceOfTheTumbleweeds card) { + super(card); + } + + @Override + public DanceOfTheTumbleweeds copy() { + return new DanceOfTheTumbleweeds(this); + } +} + +class DanceOfTheTumbleweedsEffect extends OneShotEffect { + + DanceOfTheTumbleweedsEffect() { + super(Outcome.Benefit); + staticText = "create an X/X green Elemental creature token, where X is the number of lands you control"; + } + + private DanceOfTheTumbleweedsEffect(final DanceOfTheTumbleweedsEffect effect) { + super(effect); + } + + @Override + public DanceOfTheTumbleweedsEffect copy() { + return new DanceOfTheTumbleweedsEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return new SeedGuardianToken(LandsYouControlCount.instance.calculate(game, source, this)) + .putOntoBattlefield(1, game, source); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DanithaNewBenaliasLight.java b/Mage.Sets/src/mage/cards/d/DanithaNewBenaliasLight.java index ccb6f61a633..b458bd99339 100644 --- a/Mage.Sets/src/mage/cards/d/DanithaNewBenaliasLight.java +++ b/Mage.Sets/src/mage/cards/d/DanithaNewBenaliasLight.java @@ -1,25 +1,18 @@ package mage.cards.d; -import mage.MageIdentifier; import mage.MageInt; -import mage.MageObjectReference; -import mage.abilities.Ability; -import mage.abilities.SpellAbility; -import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.common.CastFromGraveyardOnceEachTurnAbility; import mage.abilities.keyword.LifelinkAbility; import mage.abilities.keyword.TrampleAbility; import mage.abilities.keyword.VigilanceAbility; -import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.watchers.Watcher; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicates; -import java.util.HashSet; -import java.util.Set; import java.util.UUID; /** @@ -27,6 +20,13 @@ import java.util.UUID; */ public final class DanithaNewBenaliasLight extends CardImpl { + private static final FilterCard filter = new FilterCard("an Aura or Equipment spell"); + static { + filter.add(Predicates.or( + SubType.AURA.getPredicate(), SubType.EQUIPMENT.getPredicate() + )); + } + public DanithaNewBenaliasLight(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}{W}"); @@ -46,9 +46,7 @@ public final class DanithaNewBenaliasLight extends CardImpl { this.addAbility(LifelinkAbility.getInstance()); // Once during each of your turns, you may cast an Aura or Equipment spell from your graveyard. - this.addAbility(new SimpleStaticAbility( - new DanithaNewBenaliasLightCastFromGraveyardEffect() - ).setIdentifier(MageIdentifier.DanithaNewBenaliasLightWatcher), new DanithaNewBenaliasLightWatcher()); + this.addAbility(new CastFromGraveyardOnceEachTurnAbility(filter)); } private DanithaNewBenaliasLight(final DanithaNewBenaliasLight card) { @@ -60,74 +58,3 @@ public final class DanithaNewBenaliasLight extends CardImpl { return new DanithaNewBenaliasLight(this); } } - -class DanithaNewBenaliasLightCastFromGraveyardEffect extends AsThoughEffectImpl { - - DanithaNewBenaliasLightCastFromGraveyardEffect() { - super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.PutCardInPlay); - staticText = "once during each of your turns, you may cast an Aura or Equipment spell from your graveyard"; - } - - private DanithaNewBenaliasLightCastFromGraveyardEffect(final DanithaNewBenaliasLightCastFromGraveyardEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - return true; - } - - @Override - public DanithaNewBenaliasLightCastFromGraveyardEffect copy() { - return new DanithaNewBenaliasLightCastFromGraveyardEffect(this); - } - - @Override - public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) { - if (!source.isControlledBy(playerId) || game.getState().getZone(objectId) != Zone.GRAVEYARD || !(affectedAbility instanceof SpellAbility)) { - return false; - } - Card objectCard = ((SpellAbility) affectedAbility).getCharacteristics(game); - return objectCard != null - && objectCard.isOwnedBy(source.getControllerId()) - && (objectCard.hasSubtype(SubType.AURA, game) || objectCard.hasSubtype(SubType.EQUIPMENT, game)) - && objectCard.getSpellAbility() != null - && objectCard.getSpellAbility().spellCanBeActivatedRegularlyNow(playerId, game) - && !DanithaNewBenaliasLightWatcher.isAbilityUsed(source, game); - } - @Override - public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { - throw new IllegalArgumentException("Wrong code usage: can't call applies method on empty affectedAbility"); - } -} - -class DanithaNewBenaliasLightWatcher extends Watcher { - - private final Set usedFrom = new HashSet<>(); - - DanithaNewBenaliasLightWatcher() { - super(WatcherScope.GAME); - } - - @Override - public void watch(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.SPELL_CAST - && event.hasApprovingIdentifier(MageIdentifier.DanithaNewBenaliasLightWatcher)) { - usedFrom.add(event.getAdditionalReference().getApprovingMageObjectReference()); - } - } - - @Override - public void reset() { - super.reset(); - usedFrom.clear(); - } - - public static boolean isAbilityUsed(Ability source, Game game) { - return game - .getState() - .getWatcher(DanithaNewBenaliasLightWatcher.class) - .usedFrom - .contains(new MageObjectReference(source.getSourceId(), game)); - } -} diff --git a/Mage.Sets/src/mage/cards/d/DarienKingOfKjeldor.java b/Mage.Sets/src/mage/cards/d/DarienKingOfKjeldor.java index 6c5f1778268..ac94d207915 100644 --- a/Mage.Sets/src/mage/cards/d/DarienKingOfKjeldor.java +++ b/Mage.Sets/src/mage/cards/d/DarienKingOfKjeldor.java @@ -16,7 +16,6 @@ import mage.constants.SuperType; import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; import mage.game.permanent.token.SoldierToken; import mage.players.Player; @@ -66,7 +65,7 @@ class DarienKingOfKjeldorTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PLAYER; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PLAYER; } @Override diff --git a/Mage.Sets/src/mage/cards/d/DaringThunderThief.java b/Mage.Sets/src/mage/cards/d/DaringThunderThief.java new file mode 100644 index 00000000000..c626227488d --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DaringThunderThief.java @@ -0,0 +1,41 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTappedAbility; +import mage.abilities.keyword.FlashAbility; +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 DaringThunderThief extends CardImpl { + + public DaringThunderThief(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}"); + + this.subtype.add(SubType.TURTLE); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // Daring Thunder-Thief enters the battlefield tapped. + this.addAbility(new EntersBattlefieldTappedAbility()); + } + + private DaringThunderThief(final DaringThunderThief card) { + super(card); + } + + @Override + public DaringThunderThief copy() { + return new DaringThunderThief(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DeadeyeDuelist.java b/Mage.Sets/src/mage/cards/d/DeadeyeDuelist.java new file mode 100644 index 00000000000..8017df62778 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DeadeyeDuelist.java @@ -0,0 +1,49 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.keyword.ReachAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DeadeyeDuelist extends CardImpl { + + public DeadeyeDuelist(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ASSASSIN); + this.power = new MageInt(1); + this.toughness = new MageInt(3); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // {1}, {T}: Deadeye Duelist deals 1 damage to target opponent. + Ability ability = new SimpleActivatedAbility(new DamageTargetEffect(1), new GenericManaCost(1)); + ability.addCost(new TapSourceCost()); + ability.addTarget(new TargetOpponent()); + this.addAbility(ability); + } + + private DeadeyeDuelist(final DeadeyeDuelist card) { + super(card); + } + + @Override + public DeadeyeDuelist copy() { + return new DeadeyeDuelist(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DecoyGambit.java b/Mage.Sets/src/mage/cards/d/DecoyGambit.java index 0736cdccef7..135434fe84d 100644 --- a/Mage.Sets/src/mage/cards/d/DecoyGambit.java +++ b/Mage.Sets/src/mage/cards/d/DecoyGambit.java @@ -9,15 +9,12 @@ import mage.cards.CardsImpl; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Zone; -import mage.filter.FilterPermanent; -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.Target; -import mage.target.TargetPermanent; -import mage.target.targetadjustment.TargetAdjuster; +import mage.target.common.TargetCreaturePermanent; +import mage.target.targetadjustment.EachOpponentPermanentTargetsAdjuster; import java.util.Collection; import java.util.List; @@ -36,7 +33,7 @@ public final class DecoyGambit extends CardImpl { // For each opponent, choose up to one target creature that player controls, // then return that creature to its owner's hand unless its controller has you draw a card. this.getSpellAbility().addEffect(new DecoyGambitEffect()); - this.getSpellAbility().setTargetAdjuster(DecoyGambitAdjuster.instance); + this.getSpellAbility().setTargetAdjuster(new EachOpponentPermanentTargetsAdjuster(new TargetCreaturePermanent(0,1))); } private DecoyGambit(final DecoyGambit card) { @@ -49,26 +46,6 @@ public final class DecoyGambit extends CardImpl { } } -enum DecoyGambitAdjuster implements TargetAdjuster { - instance; - - @Override - public void adjustTargets(Ability ability, Game game) { - ability.getTargets().clear(); - game.getOpponents(ability.getControllerId()) - .stream() - .map(game::getPlayer) - .filter(Objects::nonNull) - .forEachOrdered(player -> { - FilterPermanent filter = new FilterCreaturePermanent( - "creature controlled by " + player.getName() - ); - filter.add(new ControllerIdPredicate(player.getId())); - ability.addTarget(new TargetPermanent(0, 1, filter, false)); - }); - } -} - class DecoyGambitEffect extends OneShotEffect { DecoyGambitEffect() { diff --git a/Mage.Sets/src/mage/cards/d/DeepmuckDesperado.java b/Mage.Sets/src/mage/cards/d/DeepmuckDesperado.java new file mode 100644 index 00000000000..adae010f128 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DeepmuckDesperado.java @@ -0,0 +1,41 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.common.CommittedCrimeTriggeredAbility; +import mage.abilities.effects.common.MillCardsEachPlayerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.TargetController; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DeepmuckDesperado extends CardImpl { + + public DeepmuckDesperado(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); + + this.subtype.add(SubType.HOMARID); + this.subtype.add(SubType.MERCENARY); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // Whenever you commit a crime, each opponent mills three cards. This ability triggers only once each turn. + this.addAbility(new CommittedCrimeTriggeredAbility( + new MillCardsEachPlayerEffect(3, TargetController.OPPONENT) + ).setTriggersOnceEachTurn(true)); + } + + private DeepmuckDesperado(final DeepmuckDesperado card) { + super(card); + } + + @Override + public DeepmuckDesperado copy() { + return new DeepmuckDesperado(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DeluxeDragster.java b/Mage.Sets/src/mage/cards/d/DeluxeDragster.java index c9ec81a50ab..fc787d555a3 100644 --- a/Mage.Sets/src/mage/cards/d/DeluxeDragster.java +++ b/Mage.Sets/src/mage/cards/d/DeluxeDragster.java @@ -3,7 +3,7 @@ package mage.cards.d; import mage.MageInt; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleEvasionAbility; -import mage.abilities.effects.common.MayCastTargetThenExileEffect; +import mage.abilities.effects.common.MayCastTargetCardEffect; import mage.abilities.effects.common.combat.CantBeBlockedByCreaturesSourceEffect; import mage.abilities.effects.common.replacement.ThatSpellGraveyardExileReplacementEffect; import mage.abilities.keyword.CrewAbility; @@ -65,7 +65,7 @@ public final class DeluxeDragster extends CardImpl { class DeluxeDragsterTriggeredAbility extends TriggeredAbilityImpl { DeluxeDragsterTriggeredAbility() { - super(Zone.BATTLEFIELD, new MayCastTargetThenExileEffect(true) + super(Zone.BATTLEFIELD, new MayCastTargetCardEffect(CastManaAdjustment.WITHOUT_PAYING_MANA_COST, true) .setText("you may cast target instant or sorcery card from " + "that player's graveyard without paying its mana cost. " + ThatSpellGraveyardExileReplacementEffect.RULE_A), false); diff --git a/Mage.Sets/src/mage/cards/d/DemonicRuckus.java b/Mage.Sets/src/mage/cards/d/DemonicRuckus.java new file mode 100644 index 00000000000..8a6523f099f --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DemonicRuckus.java @@ -0,0 +1,61 @@ +package mage.cards.d; + +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.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.continuous.BoostEnchantedEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.abilities.keyword.MenaceAbility; +import mage.abilities.keyword.PlotAbility; +import mage.abilities.keyword.TrampleAbility; +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 Susucr + */ +public final class DemonicRuckus extends CardImpl { + + public DemonicRuckus(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{R}"); + + 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 gets +1/+1 and has menace and trample. + Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostEnchantedEffect(1, 1)); + ability.addEffect(new GainAbilityAttachedEffect(new MenaceAbility(false), AttachmentType.AURA).setText("and has menace")); + ability.addEffect(new GainAbilityAttachedEffect(TrampleAbility.getInstance(), AttachmentType.AURA).setText("and trample")); + this.addAbility(ability); + + // When Demonic Ruckus is put into a graveyard from the battlefield, draw a card. + this.addAbility(new PutIntoGraveFromBattlefieldSourceTriggeredAbility( + new DrawCardSourceControllerEffect(1) + )); + + // Plot {R} + this.addAbility(new PlotAbility("{R}")); + } + + private DemonicRuckus(final DemonicRuckus card) { + super(card); + } + + @Override + public DemonicRuckus copy() { + return new DemonicRuckus(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DesecrateReality.java b/Mage.Sets/src/mage/cards/d/DesecrateReality.java index 4e5015d26c3..e92fb2e4388 100644 --- a/Mage.Sets/src/mage/cards/d/DesecrateReality.java +++ b/Mage.Sets/src/mage/cards/d/DesecrateReality.java @@ -13,16 +13,14 @@ import mage.constants.CardType; import mage.constants.Outcome; import mage.filter.FilterCard; import mage.filter.FilterPermanent; -import mage.filter.common.FilterControlledPermanent; import mage.filter.common.FilterPermanentCard; import mage.filter.predicate.mageobject.ManaValueParityPredicate; -import mage.filter.predicate.permanent.ControllerIdPredicate; import mage.game.Game; import mage.players.Player; import mage.target.Target; import mage.target.TargetPermanent; import mage.target.common.TargetCardInYourGraveyard; -import mage.target.targetadjustment.TargetAdjuster; +import mage.target.targetadjustment.EachOpponentPermanentTargetsAdjuster; import mage.target.targetpointer.EachTargetPointer; import mage.target.targetpointer.FixedTarget; @@ -33,6 +31,11 @@ import java.util.UUID; */ public final class DesecrateReality extends CardImpl { + private static final FilterPermanent evenFilter = new FilterPermanent("permanent with an even mana value"); + + static { + evenFilter.add(ManaValueParityPredicate.EVEN); + } public DesecrateReality(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{7}"); @@ -41,7 +44,7 @@ public final class DesecrateReality extends CardImpl { this.getSpellAbility().addEffect(new ExileTargetEffect() .setTargetPointer(new EachTargetPointer()) .setText("for each opponent, exile up to one target permanent that player controls with an even mana value.")); - this.getSpellAbility().setTargetAdjuster(DesecrateRealityAdjuster.instance); + this.getSpellAbility().setTargetAdjuster(new EachOpponentPermanentTargetsAdjuster(new TargetPermanent(0, 1, evenFilter))); // Adamant -- If at least three colorless mana was spent to cast this spell, return a permanent card with an odd mana value from your graveyard to the battlefield. this.getSpellAbility().addEffect(new ConditionalOneShotEffect( @@ -62,36 +65,6 @@ public final class DesecrateReality extends CardImpl { } } -enum DesecrateRealityAdjuster implements TargetAdjuster { - instance; - - private static final FilterControlledPermanent filterCount = new FilterControlledPermanent(""); - - static { - filterCount.add(ManaValueParityPredicate.EVEN); - } - - @Override - public void adjustTargets(Ability ability, Game game) { - ability.getTargets().clear(); - for (UUID opponentId : game.getOpponents(ability.getControllerId())) { - Player opponent = game.getPlayer(opponentId); - - if (opponent == null || game.getBattlefield().count( - filterCount, - opponentId, ability, game - ) < 1) { - continue; - } - FilterPermanent filter = new FilterPermanent("permanent controlled by " + opponent.getName() + " with an even mana value."); - filter.add(new ControllerIdPredicate(opponentId)); - filter.add(ManaValueParityPredicate.EVEN); - TargetPermanent targetPermanent = new TargetPermanent(0, 1, filter); - ability.addTarget(targetPermanent); - } - } -} - class DesecrateRealityEffect extends OneShotEffect { private static final FilterCard filter diff --git a/Mage.Sets/src/mage/cards/d/DesertsDue.java b/Mage.Sets/src/mage/cards/d/DesertsDue.java new file mode 100644 index 00000000000..dd9750b66c1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DesertsDue.java @@ -0,0 +1,49 @@ +package mage.cards.d; + +import mage.abilities.dynamicvalue.AdditiveDynamicValue; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.dynamicvalue.common.SignInversionDynamicValue; +import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.common.FilterControlledPermanent; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DesertsDue extends CardImpl { + + private static final DynamicValue desertCount = new PermanentsOnBattlefieldCount(new FilterControlledPermanent(SubType.DESERT)); + private static final DynamicValue xValue = new AdditiveDynamicValue( + new SignInversionDynamicValue(desertCount), StaticValue.get(-2) + ); + private static final Hint hint = new ValueHint("Deserts you control", desertCount); + + public DesertsDue(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{B}"); + + // Target creature gets -2/-2 until end of turn. It gets an additional -1/-1 until end of turn for each Desert you control. + this.getSpellAbility().addEffect(new BoostTargetEffect(xValue, xValue) + .setText("target creature gets -2/-2 until end of turn. " + + "It gets an additional -1/-1 until end of turn for each Desert you control")); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + } + + private DesertsDue(final DesertsDue card) { + super(card); + } + + @Override + public DesertsDue copy() { + return new DesertsDue(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DesperateBloodseeker.java b/Mage.Sets/src/mage/cards/d/DesperateBloodseeker.java new file mode 100644 index 00000000000..8aab299cdc8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DesperateBloodseeker.java @@ -0,0 +1,45 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.MillCardsTargetEffect; +import mage.abilities.keyword.LifelinkAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.TargetPlayer; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DesperateBloodseeker extends CardImpl { + + public DesperateBloodseeker(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}"); + + this.subtype.add(SubType.VAMPIRE); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // When Desperate Bloodseeker enters the battlefield, target player mills two cards. + Ability ability = new EntersBattlefieldTriggeredAbility(new MillCardsTargetEffect(2)); + ability.addTarget(new TargetPlayer()); + this.addAbility(ability); + } + + private DesperateBloodseeker(final DesperateBloodseeker card) { + super(card); + } + + @Override + public DesperateBloodseeker copy() { + return new DesperateBloodseeker(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DevouringHellion.java b/Mage.Sets/src/mage/cards/d/DevouringHellion.java index 5a547c90b37..b9eae227c87 100644 --- a/Mage.Sets/src/mage/cards/d/DevouringHellion.java +++ b/Mage.Sets/src/mage/cards/d/DevouringHellion.java @@ -18,7 +18,7 @@ import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.Target; -import mage.target.TargetPermanent; +import mage.target.common.TargetSacrifice; import java.util.UUID; @@ -80,8 +80,8 @@ class DevouringHellionEffect extends OneShotEffect { if (player == null) { return false; } - Target target = new TargetPermanent(0, Integer.MAX_VALUE, filter, true); - if (!player.choose(outcome, target, source, game)) { + Target target = new TargetSacrifice(0, Integer.MAX_VALUE, filter); + if (!player.choose(Outcome.Sacrifice, target, source, game)) { return false; } int xValue = 0; @@ -93,4 +93,4 @@ class DevouringHellionEffect extends OneShotEffect { } return new AddCountersSourceEffect(CounterType.P1P1.createInstance(2 * xValue)).apply(game, source); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/d/DiluvianPrimordial.java b/Mage.Sets/src/mage/cards/d/DiluvianPrimordial.java index 50c8139adc6..61f8053ed07 100644 --- a/Mage.Sets/src/mage/cards/d/DiluvianPrimordial.java +++ b/Mage.Sets/src/mage/cards/d/DiluvianPrimordial.java @@ -4,12 +4,13 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.MayCastTargetThenExileEffect; +import mage.abilities.effects.common.MayCastTargetCardEffect; import mage.abilities.keyword.FlyingAbility; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.CastManaAdjustment; import mage.constants.Outcome; import mage.constants.SubType; import mage.filter.FilterCard; @@ -106,7 +107,8 @@ class DiluvianPrimordialEffect extends OneShotEffect { if (target instanceof TargetCardInOpponentsGraveyard) { Card targetCard = game.getCard(target.getFirstTarget()); if (targetCard != null) { - new MayCastTargetThenExileEffect(true).setTargetPointer(new FixedTarget(targetCard, game)).apply(game, source); + new MayCastTargetCardEffect(CastManaAdjustment.WITHOUT_PAYING_MANA_COST, true) + .setTargetPointer(new FixedTarget(targetCard, game)).apply(game, source); } } } diff --git a/Mage.Sets/src/mage/cards/d/DiscerningPeddler.java b/Mage.Sets/src/mage/cards/d/DiscerningPeddler.java new file mode 100644 index 00000000000..243e95c6f4a --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DiscerningPeddler.java @@ -0,0 +1,42 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.common.DiscardCardCost; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +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 DiscerningPeddler extends CardImpl { + + public DiscerningPeddler(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // When Discerning Peddler enters the battlefield, you may discard a card. If you do, draw a card. + this.addAbility(new EntersBattlefieldTriggeredAbility( + new DoIfCostPaid(new DrawCardSourceControllerEffect(1), new DiscardCardCost()) + )); + } + + private DiscerningPeddler(final DiscerningPeddler card) { + super(card); + } + + @Override + public DiscerningPeddler copy() { + return new DiscerningPeddler(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DismantlingWave.java b/Mage.Sets/src/mage/cards/d/DismantlingWave.java index 76bfe9f0e73..4625feede73 100644 --- a/Mage.Sets/src/mage/cards/d/DismantlingWave.java +++ b/Mage.Sets/src/mage/cards/d/DismantlingWave.java @@ -1,6 +1,5 @@ package mage.cards.d; -import mage.abilities.Ability; import mage.abilities.common.CycleTriggeredAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.DestroyAllEffect; @@ -9,16 +8,11 @@ import mage.abilities.keyword.CyclingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.FilterPermanent; import mage.filter.StaticFilters; -import mage.filter.common.FilterArtifactOrEnchantmentPermanent; -import mage.filter.predicate.permanent.ControllerIdPredicate; -import mage.game.Game; import mage.target.TargetPermanent; -import mage.target.targetadjustment.TargetAdjuster; +import mage.target.targetadjustment.EachOpponentPermanentTargetsAdjuster; import mage.target.targetpointer.EachTargetPointer; -import java.util.Objects; import java.util.UUID; /** @@ -33,7 +27,8 @@ public final class DismantlingWave extends CardImpl { this.getSpellAbility().addEffect(new DestroyTargetEffect() .setTargetPointer(new EachTargetPointer()) .setText("For each opponent, destroy up to one target artifact or enchantment that player controls.")); - this.getSpellAbility().setTargetAdjuster(DismantlingWaveAdjuster.instance); + this.getSpellAbility().setTargetAdjuster(new EachOpponentPermanentTargetsAdjuster( + new TargetPermanent(0, 1, StaticFilters.FILTER_PERMANENT_ARTIFACT_OR_ENCHANTMENT))); // Cycling {6}{W}{W} this.addAbility(new CyclingAbility(new ManaCostsImpl<>("{6}{W}{W}"))); @@ -51,23 +46,3 @@ public final class DismantlingWave extends CardImpl { return new DismantlingWave(this); } } - -enum DismantlingWaveAdjuster implements TargetAdjuster { - instance; - - @Override - public void adjustTargets(Ability ability, Game game) { - ability.getTargets().clear(); - game.getOpponents(ability.getControllerId()) - .stream() - .map(game::getPlayer) - .filter(Objects::nonNull) - .forEachOrdered(player -> { - FilterPermanent filter = new FilterArtifactOrEnchantmentPermanent( - "artifact or enchantment controlled by " + player.getName() - ); - filter.add(new ControllerIdPredicate(player.getId())); - ability.addTarget(new TargetPermanent(0, 1, filter, false)); - }); - } -} diff --git a/Mage.Sets/src/mage/cards/d/DisorderInTheCourt.java b/Mage.Sets/src/mage/cards/d/DisorderInTheCourt.java index 0b2b7b32bd8..993de177287 100644 --- a/Mage.Sets/src/mage/cards/d/DisorderInTheCourt.java +++ b/Mage.Sets/src/mage/cards/d/DisorderInTheCourt.java @@ -10,7 +10,6 @@ import mage.abilities.effects.keyword.InvestigateEffect; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.cards.CardsImpl; import mage.constants.CardType; import mage.constants.Outcome; import mage.game.Game; @@ -93,7 +92,10 @@ class DisorderInTheCourtEffect extends OneShotEffect { new InvestigateEffect(ManacostVariableValue.REGULAR).apply(game, source); if (!toExile.isEmpty()) { Effect effect = new ReturnToBattlefieldUnderOwnerControlTargetEffect(true, true); - effect.setTargetPointer(new FixedTargets(new CardsImpl(toExile), game)); + effect.setTargetPointer(new FixedTargets(toExile + .stream() + .map(Card::getMainCard) + .collect(Collectors.toSet()), game)); game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect), source); } return true; diff --git a/Mage.Sets/src/mage/cards/d/DjinnOfFoolsFall.java b/Mage.Sets/src/mage/cards/d/DjinnOfFoolsFall.java new file mode 100644 index 00000000000..c17e9e66dff --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DjinnOfFoolsFall.java @@ -0,0 +1,40 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.PlotAbility; +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 DjinnOfFoolsFall extends CardImpl { + + public DjinnOfFoolsFall(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{U}"); + + this.subtype.add(SubType.DJINN); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Plot {3}{U} + this.addAbility(new PlotAbility("{3}{U}")); + } + + private DjinnOfFoolsFall(final DjinnOfFoolsFall card) { + super(card); + } + + @Override + public DjinnOfFoolsFall copy() { + return new DjinnOfFoolsFall(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DocAurlockGrizzledGenius.java b/Mage.Sets/src/mage/cards/d/DocAurlockGrizzledGenius.java new file mode 100644 index 00000000000..362f1cf2990 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DocAurlockGrizzledGenius.java @@ -0,0 +1,106 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.cost.CostModificationEffectImpl; +import mage.abilities.effects.common.cost.SpellsCostReductionControllerEffect; +import mage.abilities.keyword.PlotAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.card.CastFromZonePredicate; +import mage.game.Game; +import mage.players.Player; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class DocAurlockGrizzledGenius extends CardImpl { + + private static final FilterCard filter = new FilterCard("Spells you cast from your graveyard or from exile"); + + static { + filter.add(Predicates.or( + new CastFromZonePredicate(Zone.GRAVEYARD), + new CastFromZonePredicate(Zone.EXILED) + )); + } + + public DocAurlockGrizzledGenius(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.BEAR); + this.subtype.add(SubType.DRUID); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Spells you cast from your graveyard or from exile cost {2} less to cast. + this.addAbility(new SimpleStaticAbility(new SpellsCostReductionControllerEffect(filter, 2))); + + // Plotting cards from your hand costs {2} less. + this.addAbility(new SimpleStaticAbility(new DocAurlockGrizzledGeniusEffect())); + } + + private DocAurlockGrizzledGenius(final DocAurlockGrizzledGenius card) { + super(card); + } + + @Override + public DocAurlockGrizzledGenius copy() { + return new DocAurlockGrizzledGenius(this); + } +} + +/** + * Inspired by {@link mage.cards.f.Fluctuator} + */ +class DocAurlockGrizzledGeniusEffect extends CostModificationEffectImpl { + + public DocAurlockGrizzledGeniusEffect() { + super(Duration.Custom, Outcome.Benefit, CostModificationType.REDUCE_COST); + staticText = "Plotting cards from your hand costs {2} less"; + } + + private DocAurlockGrizzledGeniusEffect(final DocAurlockGrizzledGeniusEffect effect) { + super(effect); + } + + @Override + public DocAurlockGrizzledGeniusEffect copy() { + return new DocAurlockGrizzledGeniusEffect(this); + } + + @Override + public boolean applies(Ability abilityToModify, Ability source, Game game) { + return abilityToModify.isControlledBy(source.getControllerId()) + && (abilityToModify instanceof PlotAbility); + } + + @Override + public boolean apply(Game game, Ability source, Ability abilityToModify) { + if (!Zone.HAND.equals(game.getState().getZone(abilityToModify.getSourceId()))) { + return false; + } + Player controller = game.getPlayer(abilityToModify.getControllerId()); + if (controller != null) { + Mana mana = abilityToModify.getManaCostsToPay().getMana(); + int reduce = mana.getGeneric(); + if (reduce > 2) { + reduce = 2; + } + if (reduce > 0) { + CardUtil.reduceCost(abilityToModify, reduce); + } + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/d/DoubleDown.java b/Mage.Sets/src/mage/cards/d/DoubleDown.java new file mode 100644 index 00000000000..792d7f77b12 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DoubleDown.java @@ -0,0 +1,43 @@ +package mage.cards.d; + +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.common.CopyTargetSpellEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SetTargetPointer; +import mage.filter.FilterSpell; +import mage.filter.predicate.mageobject.OutlawPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DoubleDown extends CardImpl { + + private static final FilterSpell filter = new FilterSpell("an outlaw spell"); + + static { + filter.add(OutlawPredicate.instance); + } + + public DoubleDown(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{U}"); + + // Whenever you cast an outlaw spell, copy that spell. + this.addAbility(new SpellCastControllerTriggeredAbility( + new CopyTargetSpellEffect(false, false, false), + filter, false, SetTargetPointer.SPELL + )); + } + + private DoubleDown(final DoubleDown card) { + super(card); + } + + @Override + public DoubleDown copy() { + return new DoubleDown(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DreadhordeArcanist.java b/Mage.Sets/src/mage/cards/d/DreadhordeArcanist.java index 417a5628ae6..9ba2da0ef49 100644 --- a/Mage.Sets/src/mage/cards/d/DreadhordeArcanist.java +++ b/Mage.Sets/src/mage/cards/d/DreadhordeArcanist.java @@ -3,11 +3,12 @@ package mage.cards.d; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.AttacksTriggeredAbility; -import mage.abilities.effects.common.MayCastTargetThenExileEffect; +import mage.abilities.effects.common.MayCastTargetCardEffect; import mage.abilities.keyword.TrampleAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.CastManaAdjustment; import mage.constants.SubType; import mage.filter.FilterCard; import mage.filter.common.FilterInstantOrSorceryCard; @@ -42,7 +43,7 @@ public final class DreadhordeArcanist extends CardImpl { // Whenever Dreadhorde Arcanist attacks, you may cast target instant or sorcery card with converted mana cost less than or equal to Dreadhorde Arcanist's power from your graveyard without paying its mana cost. // If that card would be put into your graveyard this turn, exile it instead. - Ability ability = new AttacksTriggeredAbility(new MayCastTargetThenExileEffect(true), false); + Ability ability = new AttacksTriggeredAbility(new MayCastTargetCardEffect(CastManaAdjustment.WITHOUT_PAYING_MANA_COST, true), false); ability.addTarget(new TargetCardInYourGraveyard(filter)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/d/DreamChisel.java b/Mage.Sets/src/mage/cards/d/DreamChisel.java index 86db355d011..1c384389294 100644 --- a/Mage.Sets/src/mage/cards/d/DreamChisel.java +++ b/Mage.Sets/src/mage/cards/d/DreamChisel.java @@ -1,7 +1,7 @@ package mage.cards.d; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.common.cost.MorphSpellsCostReductionControllerEffect; +import mage.abilities.effects.common.cost.FaceDownSpellsCostReductionControllerEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -17,7 +17,7 @@ public final class DreamChisel extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); // Face-down creature spells you cast cost {1} less to cast. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new MorphSpellsCostReductionControllerEffect(1))); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new FaceDownSpellsCostReductionControllerEffect(1))); } private DreamChisel(final DreamChisel card) { @@ -28,4 +28,4 @@ public final class DreamChisel extends CardImpl { public DreamChisel copy() { return new DreamChisel(this); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/d/DroverGrizzly.java b/Mage.Sets/src/mage/cards/d/DroverGrizzly.java new file mode 100644 index 00000000000..053a7465c22 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DroverGrizzly.java @@ -0,0 +1,48 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.common.AttacksWhileSaddledTriggeredAbility; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.SaddleAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DroverGrizzly extends CardImpl { + + public DroverGrizzly(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.subtype.add(SubType.BEAR); + this.subtype.add(SubType.MOUNT); + this.power = new MageInt(4); + this.toughness = new MageInt(2); + + // Whenever Drover Grizzly attacks while saddled, creatures you control gain trample until end of turn. + this.addAbility(new AttacksWhileSaddledTriggeredAbility(new GainAbilityControlledEffect( + TrampleAbility.getInstance(), Duration.EndOfTurn, + StaticFilters.FILTER_PERMANENT_CREATURES + ))); + + // Saddle 1 + this.addAbility(new SaddleAbility(1)); + } + + private DroverGrizzly(final DroverGrizzly card) { + super(card); + } + + @Override + public DroverGrizzly copy() { + return new DroverGrizzly(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DuelistOfTheMind.java b/Mage.Sets/src/mage/cards/d/DuelistOfTheMind.java new file mode 100644 index 00000000000..f3034f78410 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DuelistOfTheMind.java @@ -0,0 +1,58 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.common.CommittedCrimeTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.common.CardsDrawnThisTurnDynamicValue; +import mage.abilities.effects.common.DrawDiscardControllerEffect; +import mage.abilities.effects.common.continuous.SetBasePowerSourceEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Zone; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DuelistOfTheMind extends CardImpl { + + public DuelistOfTheMind(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ADVISOR); + this.power = new MageInt(0); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Duelist of the Mind's power is equal to the number of cards you've drawn this turn. + this.addAbility(new SimpleStaticAbility( + Zone.ALL, new SetBasePowerSourceEffect(CardsDrawnThisTurnDynamicValue.instance) + .setText("{this}'s power is equal to the number of cards you've drawn this turn") + ).addHint(CardsDrawnThisTurnDynamicValue.getHint())); + + // Whenever you commit a crime, you may draw a card. If you do, discard a card. This ability triggers only once each turn. + this.addAbility(new CommittedCrimeTriggeredAbility( + new DrawDiscardControllerEffect(1, 1, true), false + ).setTriggersOnceEachTurn(true)); + } + + private DuelistOfTheMind(final DuelistOfTheMind card) { + super(card); + } + + @Override + public DuelistOfTheMind copy() { + return new DuelistOfTheMind(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DuneChanter.java b/Mage.Sets/src/mage/cards/d/DuneChanter.java new file mode 100644 index 00000000000..23a1c23af93 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DuneChanter.java @@ -0,0 +1,174 @@ +package mage.cards.d; + +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.effects.ContinuousEffectImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.ReachAbility; +import mage.abilities.mana.AnyColorManaAbility; +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.FilterOwnedCard; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; + +import java.util.List; +import java.util.UUID; + +/** + * @author Susucr + */ +public final class DuneChanter extends CardImpl { + + public DuneChanter(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.subtype.add(SubType.PLANT); + this.subtype.add(SubType.DRUID); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // Lands you control and land cards you own that aren't on the battlefield are Deserts in addition to their other types. + this.addAbility(new SimpleStaticAbility(new DuneChanterContinuousEffect())); + + // Lands you control have "{T}: Add one mana of any color." + this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect( + new AnyColorManaAbility(), Duration.WhileOnBattlefield, + StaticFilters.FILTER_LANDS, false + ))); + + // {T}: Mill two cards. You gain 1 life for each land card milled this way. + this.addAbility(new SimpleActivatedAbility(new DuneChanterEffect(), new TapSourceCost())); + } + + private DuneChanter(final DuneChanter card) { + super(card); + } + + @Override + public DuneChanter copy() { + return new DuneChanter(this); + } +} + +class DuneChanterContinuousEffect extends ContinuousEffectImpl { + private static final FilterPermanent filterPermanent = StaticFilters.FILTER_CONTROLLED_PERMANENT_LANDS; + private static final FilterOwnedCard filterCard = new FilterOwnedCard("land cards you own that aren't on the battlefield"); + private static final SubType subType = SubType.DESERT; + + static { + filterCard.add(CardType.LAND.getPredicate()); + } + + public DuneChanterContinuousEffect() { + super(Duration.WhileOnBattlefield, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Benefit); + staticText = "Lands you control and land cards you own that aren't on the battlefield are Deserts in addition to their other types"; + } + + private DuneChanterContinuousEffect(final DuneChanterContinuousEffect effect) { + super(effect); + } + + @Override + public DuneChanterContinuousEffect copy() { + return new DuneChanterContinuousEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + UUID controllerId = source.getControllerId(); + Player controller = game.getPlayer(controllerId); + if (controller == null) { + return false; + } + + // lands cards you own that aren't on the battlefield + // in graveyard + for (UUID cardId : controller.getGraveyard()) { + Card card = game.getCard(cardId); + if (filterCard.match(card, controllerId, source, game) && !card.hasSubtype(subType, game)) { + game.getState().getCreateMageObjectAttribute(card, game).getSubtype().add(subType); + } + } + // on hand + for (UUID cardId : controller.getHand()) { + Card card = game.getCard(cardId); + if (filterCard.match(card, controllerId, source, game) && !card.hasSubtype(subType, game)) { + game.getState().getCreateMageObjectAttribute(card, game).getSubtype().add(subType); + } + } + // in exile + for (Card card : game.getState().getExile().getAllCards(game, controllerId)) { + if (filterCard.match(card, controllerId, source, game) && !card.hasSubtype(subType, game)) { + game.getState().getCreateMageObjectAttribute(card, game).getSubtype().add(subType); + } + } + // in library + for (Card card : controller.getLibrary().getCards(game)) { + if (filterCard.match(card, controllerId, source, game) && !card.hasSubtype(subType, game)) { + game.getState().getCreateMageObjectAttribute(card, game).getSubtype().add(subType); + } + } + + // lands you control + List lands = game.getBattlefield().getAllActivePermanents( + filterPermanent, controllerId, game); + for (Permanent land : lands) { + if (land != null) { + land.addSubType(game, subType); + } + } + return true; + } +} + +class DuneChanterEffect extends OneShotEffect { + + DuneChanterEffect() { + super(Outcome.Benefit); + staticText = "mill two cards. You gain 1 life for each land card milled this way."; + } + + private DuneChanterEffect(final DuneChanterEffect effect) { + super(effect); + } + + @Override + public DuneChanterEffect copy() { + return new DuneChanterEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + int lifeToGain = player + .millCards(2, source, game) + .getCards(game) + .stream() + .filter(c -> c.isLand(game)) + .mapToInt(c -> game.getState().getZone(c.getId()) == Zone.GRAVEYARD ? 1 : 0) + .sum(); + if (lifeToGain > 0) { + new GainLifeEffect(lifeToGain).apply(game, source); + } + return true; + } +} + + diff --git a/Mage.Sets/src/mage/cards/d/DustAnimus.java b/Mage.Sets/src/mage/cards/d/DustAnimus.java new file mode 100644 index 00000000000..127c4926396 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DustAnimus.java @@ -0,0 +1,70 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.PlotAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledLandPermanent; +import mage.filter.predicate.permanent.TappedPredicate; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class DustAnimus extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledLandPermanent("untapped lands"); + + static { + filter.add(TappedPredicate.UNTAPPED); + } + + private static final Condition condition = new PermanentsOnTheBattlefieldCondition(filter, ComparisonType.OR_GREATER, 5); + + public DustAnimus(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.subtype.add(SubType.SPIRIT); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // If you control five or more untapped lands, Dust Animus enters the battlefield with two +1/+1 counters and a lifelink counter on it. + Ability ability = new EntersBattlefieldAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance(2)), + condition, + "If you control five or more untapped lands, " + + "{this} enters the battlefield with two +1/+1 counters " + + "and a lifelink counter on it.", + "" + ); + ability.addEffect(new AddCountersSourceEffect(CounterType.LIFELINK.createInstance())); + this.addAbility(ability); + + // Plot {1}{W} + this.addAbility(new PlotAbility("{1}{W}")); + } + + private DustAnimus(final DustAnimus card) { + super(card); + } + + @Override + public DustAnimus copy() { + return new DustAnimus(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/EfreetFlamepainter.java b/Mage.Sets/src/mage/cards/e/EfreetFlamepainter.java index cd08bc3c776..429a6e36cf8 100644 --- a/Mage.Sets/src/mage/cards/e/EfreetFlamepainter.java +++ b/Mage.Sets/src/mage/cards/e/EfreetFlamepainter.java @@ -3,11 +3,12 @@ package mage.cards.e; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; -import mage.abilities.effects.common.MayCastTargetThenExileEffect; +import mage.abilities.effects.common.MayCastTargetCardEffect; import mage.abilities.keyword.DoubleStrikeAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.CastManaAdjustment; import mage.constants.SubType; import mage.filter.StaticFilters; import mage.target.common.TargetCardInYourGraveyard; @@ -21,7 +22,7 @@ public final class EfreetFlamepainter extends CardImpl { public EfreetFlamepainter(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}"); - + this.subtype.add(SubType.EFREET); this.subtype.add(SubType.SHAMAN); this.power = new MageInt(1); @@ -31,7 +32,7 @@ public final class EfreetFlamepainter extends CardImpl { this.addAbility(DoubleStrikeAbility.getInstance()); // Whenever Efreet Flamepainter deals combat damage to a player, you may cast target instant or sorcery card from your graveyard without paying its mana cost. If that spell would be put into your graveyard, exile it instead. - Ability ability = new DealsCombatDamageToAPlayerTriggeredAbility(new MayCastTargetThenExileEffect(true), false); + Ability ability = new DealsCombatDamageToAPlayerTriggeredAbility(new MayCastTargetCardEffect(CastManaAdjustment.WITHOUT_PAYING_MANA_COST, true), false); ability.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY_FROM_YOUR_GRAVEYARD)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/e/ElderOwynLyons.java b/Mage.Sets/src/mage/cards/e/ElderOwynLyons.java new file mode 100644 index 00000000000..3fa01c47e8e --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/ElderOwynLyons.java @@ -0,0 +1,57 @@ +package mage.cards.e; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldOrDiesSourceTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.WardAbility; +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.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author notgreat + */ +public final class ElderOwynLyons extends CardImpl { + + public ElderOwynLyons(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.KNIGHT); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Artifacts you control have ward {1}. + this.addAbility(new SimpleStaticAbility( + new GainAbilityControlledEffect( + new WardAbility(new GenericManaCost(1)), Duration.WhileOnBattlefield, StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACTS + ) + )); + + // When Elder Owyn Lyons enters the battlefield or dies, return target artifact card from your graveyard to your hand. + Ability ability = new EntersBattlefieldOrDiesSourceTriggeredAbility(new ReturnFromGraveyardToHandTargetEffect(), false); + ability.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_ARTIFACT_FROM_YOUR_GRAVEYARD)); + this.addAbility(ability); + } + + private ElderOwynLyons(final ElderOwynLyons card) { + super(card); + } + + @Override + public ElderOwynLyons copy() { + return new ElderOwynLyons(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/Electrosiphon.java b/Mage.Sets/src/mage/cards/e/Electrosiphon.java new file mode 100644 index 00000000000..a9cb0184deb --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/Electrosiphon.java @@ -0,0 +1,66 @@ +package mage.cards.e; + +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.counter.GetEnergyCountersControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.game.Game; +import mage.game.stack.Spell; +import mage.target.TargetSpell; + +/** + * @author Cguy7777 + */ +public final class Electrosiphon extends CardImpl { + + public Electrosiphon(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{U}{U}{R}"); + + // Counter target spell. You get an amount of {E} equal to its mana value. + this.getSpellAbility().addTarget(new TargetSpell()); + this.getSpellAbility().addEffect(new ElectrosiphonEffect()); + } + + private Electrosiphon(final Electrosiphon card) { + super(card); + } + + @Override + public Electrosiphon copy() { + return new Electrosiphon(this); + } +} + +class ElectrosiphonEffect extends OneShotEffect { + + ElectrosiphonEffect() { + super(Outcome.Detriment); + this.staticText = "Counter target spell. You get an amount of {E} (energy counters) equal to its mana value"; + } + + private ElectrosiphonEffect(final ElectrosiphonEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Spell spell = game.getStack().getSpell(getTargetPointer().getFirst(game, source)); + if (spell == null) { + return false; + } + + game.getStack().counter(spell.getId(), source, game); + new GetEnergyCountersControllerEffect(spell.getManaValue()).apply(game, source); + return true; + } + + @Override + public ElectrosiphonEffect copy() { + return new ElectrosiphonEffect(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/ElementalEruption.java b/Mage.Sets/src/mage/cards/e/ElementalEruption.java new file mode 100644 index 00000000000..c1b902f2bc1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/ElementalEruption.java @@ -0,0 +1,35 @@ +package mage.cards.e; + +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.StormAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.game.permanent.token.DragonElementalToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ElementalEruption extends CardImpl { + + public ElementalEruption(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{R}{R}"); + + // Create a 4/4 red Dragon Elemental creature token with flying and prowess. + this.getSpellAbility().addEffect(new CreateTokenEffect(new DragonElementalToken())); + + // Storm + this.addAbility(new StormAbility()); + } + + private ElementalEruption(final ElementalEruption card) { + super(card); + } + + @Override + public ElementalEruption copy() { + return new ElementalEruption(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/ElminstersSimulacrum.java b/Mage.Sets/src/mage/cards/e/ElminstersSimulacrum.java index f4147401d47..791a982ea3f 100644 --- a/Mage.Sets/src/mage/cards/e/ElminstersSimulacrum.java +++ b/Mage.Sets/src/mage/cards/e/ElminstersSimulacrum.java @@ -7,14 +7,10 @@ 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.permanent.ControllerIdPredicate; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.players.Player; -import mage.target.TargetPermanent; -import mage.target.targetadjustment.TargetAdjuster; +import mage.target.common.TargetCreaturePermanent; +import mage.target.targetadjustment.EachOpponentPermanentTargetsAdjuster; import mage.target.targetpointer.EachTargetPointer; import java.util.UUID; @@ -29,7 +25,7 @@ public final class ElminstersSimulacrum extends CardImpl { // For each opponent, you create a token that's a copy of up to one target creature that player controls. this.getSpellAbility().addEffect(new ElminstersSimulacrumAdjusterEffect()); - this.getSpellAbility().setTargetAdjuster(ElminstersSimulacrumAdjuster.instance); + this.getSpellAbility().setTargetAdjuster(new EachOpponentPermanentTargetsAdjuster(new TargetCreaturePermanent(0,1))); } private ElminstersSimulacrum(final ElminstersSimulacrum card) { @@ -42,24 +38,6 @@ public final class ElminstersSimulacrum extends CardImpl { } } -enum ElminstersSimulacrumAdjuster implements TargetAdjuster { - instance; - - @Override - public void adjustTargets(Ability ability, Game game) { - ability.getTargets().clear(); - for (UUID opponentId : game.getOpponents(ability.getControllerId())) { - Player opponent = game.getPlayer(opponentId); - if (opponent == null) { - continue; - } - FilterPermanent filter = new FilterCreaturePermanent("creature controlled by " + opponent.getLogName()); - filter.add(new ControllerIdPredicate(opponentId)); - ability.addTarget(new TargetPermanent(0, 1, filter, false)); - } - } -} - class ElminstersSimulacrumAdjusterEffect extends OneShotEffect { ElminstersSimulacrumAdjusterEffect() { diff --git a/Mage.Sets/src/mage/cards/e/ElshaOfTheInfinite.java b/Mage.Sets/src/mage/cards/e/ElshaOfTheInfinite.java index 6430b69db40..675c3ec7ee9 100644 --- a/Mage.Sets/src/mage/cards/e/ElshaOfTheInfinite.java +++ b/Mage.Sets/src/mage/cards/e/ElshaOfTheInfinite.java @@ -5,7 +5,7 @@ import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.continuous.CastAsThoughItHadFlashAllEffect; import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; -import mage.abilities.effects.common.continuous.PlayTheTopCardEffect; +import mage.abilities.effects.common.continuous.PlayFromTopOfLibraryEffect; import mage.abilities.keyword.ProwessAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -45,7 +45,7 @@ public final class ElshaOfTheInfinite extends CardImpl { this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); // You may cast noncreature spells from the top of your library. If you cast a spell this way, you may cast it as though it had flash. - Ability ability = new SimpleStaticAbility(new PlayTheTopCardEffect(TargetController.YOU, filter, false)); + Ability ability = new SimpleStaticAbility(new PlayFromTopOfLibraryEffect(filter)); ability.addEffect(new CastAsThoughItHadFlashAllEffect( Duration.WhileOnBattlefield, filter ).setText("If you cast a spell this way, you may cast it as though it had flash.")); @@ -61,4 +61,3 @@ public final class ElshaOfTheInfinite extends CardImpl { return new ElshaOfTheInfinite(this); } } - diff --git a/Mage.Sets/src/mage/cards/e/ElvenChorus.java b/Mage.Sets/src/mage/cards/e/ElvenChorus.java index a30afc59b44..364bb38b32d 100644 --- a/Mage.Sets/src/mage/cards/e/ElvenChorus.java +++ b/Mage.Sets/src/mage/cards/e/ElvenChorus.java @@ -3,13 +3,12 @@ package mage.cards.e; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; -import mage.abilities.effects.common.continuous.PlayTheTopCardEffect; +import mage.abilities.effects.common.continuous.PlayFromTopOfLibraryEffect; import mage.abilities.mana.AnyColorManaAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; -import mage.constants.TargetController; import mage.filter.FilterCard; import mage.filter.StaticFilters; import mage.filter.common.FilterCreatureCard; @@ -30,9 +29,7 @@ public final class ElvenChorus extends CardImpl { this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); // You may cast creature spells from the top of your library. - this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect( - TargetController.YOU, filter, false - ))); + this.addAbility(new SimpleStaticAbility(new PlayFromTopOfLibraryEffect(filter))); // Creatures you control have "{T}: Add one mana of any color." this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect( diff --git a/Mage.Sets/src/mage/cards/e/EmbraceTheUnknown.java b/Mage.Sets/src/mage/cards/e/EmbraceTheUnknown.java new file mode 100644 index 00000000000..058f4a50fb5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EmbraceTheUnknown.java @@ -0,0 +1,37 @@ +package mage.cards.e; + +import mage.abilities.effects.common.ExileTopXMayPlayUntilEffect; +import mage.abilities.keyword.RetraceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class EmbraceTheUnknown extends CardImpl { + + public EmbraceTheUnknown(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{R}"); + + // Exile the top two cards of your library. Until the end of your next turn, you may play those cards. + this.getSpellAbility().addEffect(new ExileTopXMayPlayUntilEffect( + 2, Duration.UntilEndOfYourNextTurn + )); + + // Retrace + this.addAbility(new RetraceAbility(this)); + } + + private EmbraceTheUnknown(final EmbraceTheUnknown card) { + super(card); + } + + @Override + public EmbraceTheUnknown copy() { + return new EmbraceTheUnknown(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/EmergentHaunting.java b/Mage.Sets/src/mage/cards/e/EmergentHaunting.java new file mode 100644 index 00000000000..c6171eebe19 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EmergentHaunting.java @@ -0,0 +1,59 @@ +package mage.cards.e; + +import mage.abilities.common.BeginningOfEndStepTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.condition.CompoundCondition; +import mage.abilities.condition.Condition; +import mage.abilities.condition.InvertCondition; +import mage.abilities.condition.common.HaventCastSpellFromHandThisTurnCondition; +import mage.abilities.condition.common.SourceMatchesFilterCondition; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.continuous.BecomesCreatureSourceEffect; +import mage.abilities.effects.keyword.SurveilEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.common.FilterCreaturePermanent; +import mage.game.permanent.token.custom.CreatureToken; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class EmergentHaunting extends CardImpl { + + private static final Condition condition = new CompoundCondition( + "if you haven't cast a spell from your hand this turn and {this} isn't a creature", + HaventCastSpellFromHandThisTurnCondition.instance, + new InvertCondition(new SourceMatchesFilterCondition(new FilterCreaturePermanent())) + ); + + public EmergentHaunting(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{U}"); + + // At the beginning of your end step, if you haven't cast a spell from your hand this turn and Emergent Haunting isn't a creature, it becomes a 3/3 Spirit creature with flying in addition to its other types. + this.addAbility(new BeginningOfEndStepTriggeredAbility( + Zone.BATTLEFIELD, + new BecomesCreatureSourceEffect( + new CreatureToken(3, 3, "3/3 Spirit creature with flying in addition to its other types") + .withSubType(SubType.SPIRIT).withAbility(FlyingAbility.getInstance()), + null, Duration.WhileOnBattlefield + ), + TargetController.YOU, condition, false + ).withRuleTextReplacement(true).addHint(HaventCastSpellFromHandThisTurnCondition.hint)); + + // {2}{U}: Surveil 1. + this.addAbility(new SimpleActivatedAbility(new SurveilEffect(1), new ManaCostsImpl<>("{2}{U}"))); + } + + private EmergentHaunting(final EmergentHaunting card) { + super(card); + } + + @Override + public EmergentHaunting copy() { + return new EmergentHaunting(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/EmperorMihailII.java b/Mage.Sets/src/mage/cards/e/EmperorMihailII.java index 01523fcafe9..202eb5da35b 100644 --- a/Mage.Sets/src/mage/cards/e/EmperorMihailII.java +++ b/Mage.Sets/src/mage/cards/e/EmperorMihailII.java @@ -7,13 +7,12 @@ import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.DoIfCostPaid; import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; -import mage.abilities.effects.common.continuous.PlayTheTopCardEffect; +import mage.abilities.effects.common.continuous.PlayFromTopOfLibraryEffect; 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.FilterSpell; import mage.game.permanent.token.MerfolkToken; @@ -46,9 +45,7 @@ public final class EmperorMihailII extends CardImpl { this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); // You may cast Merfolk spells from the top of your library. - this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect( - TargetController.YOU, filter, false - ))); + this.addAbility(new SimpleStaticAbility(new PlayFromTopOfLibraryEffect(filter))); // Whenever you cast a Merfolk spell, you may pay {1}. If you do, create a 1/1 blue Merfolk creature token. this.addAbility(new SpellCastControllerTriggeredAbility(new DoIfCostPaid( diff --git a/Mage.Sets/src/mage/cards/e/EnigmaThief.java b/Mage.Sets/src/mage/cards/e/EnigmaThief.java index f6f0482af51..d67f142d888 100644 --- a/Mage.Sets/src/mage/cards/e/EnigmaThief.java +++ b/Mage.Sets/src/mage/cards/e/EnigmaThief.java @@ -10,13 +10,8 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.FilterPermanent; -import mage.filter.common.FilterNonlandPermanent; -import mage.filter.predicate.permanent.ControllerIdPredicate; -import mage.game.Game; -import mage.players.Player; -import mage.target.TargetPermanent; -import mage.target.targetadjustment.TargetAdjuster; +import mage.target.common.TargetNonlandPermanent; +import mage.target.targetadjustment.EachOpponentPermanentTargetsAdjuster; import mage.target.targetpointer.EachTargetPointer; import java.util.UUID; @@ -44,7 +39,7 @@ public final class EnigmaThief extends CardImpl { Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnToHandTargetEffect() .setTargetPointer(new EachTargetPointer()) .setText("for each opponent, return up to one target nonland permanent that player controls to its owner's hand")); - ability.setTargetAdjuster(EnigmaThiefAdjuster.instance); + ability.setTargetAdjuster(new EachOpponentPermanentTargetsAdjuster(new TargetNonlandPermanent(0,1))); this.addAbility(ability); } @@ -58,20 +53,3 @@ public final class EnigmaThief extends CardImpl { } } -enum EnigmaThiefAdjuster implements TargetAdjuster { - instance; - - @Override - public void adjustTargets(Ability ability, Game game) { - ability.getTargets().clear(); - for (UUID opponentId : game.getOpponents(ability.getControllerId())) { - Player opponent = game.getPlayer(opponentId); - if (opponent == null) { - continue; - } - FilterPermanent filter = new FilterNonlandPermanent("nonland permanent controlled by " + opponent.getLogName()); - filter.add(new ControllerIdPredicate(opponentId)); - ability.addTarget(new TargetPermanent(0, 1, filter, false)); - } - } -} diff --git a/Mage.Sets/src/mage/cards/e/Entomb.java b/Mage.Sets/src/mage/cards/e/Entomb.java index aeff5ad3242..4517331235d 100644 --- a/Mage.Sets/src/mage/cards/e/Entomb.java +++ b/Mage.Sets/src/mage/cards/e/Entomb.java @@ -16,7 +16,7 @@ public final class Entomb extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{B}"); // Search your library for a card and put that card into your graveyard. Then shuffle your library. - this.getSpellAbility().addEffect(new SearchLibraryPutInGraveyardEffect()); + this.getSpellAbility().addEffect(new SearchLibraryPutInGraveyardEffect(true)); } private Entomb(final Entomb card) { diff --git a/Mage.Sets/src/mage/cards/e/Epochrasite.java b/Mage.Sets/src/mage/cards/e/Epochrasite.java index ced2a932098..29533196bf4 100644 --- a/Mage.Sets/src/mage/cards/e/Epochrasite.java +++ b/Mage.Sets/src/mage/cards/e/Epochrasite.java @@ -1,26 +1,17 @@ package mage.cards.e; import mage.MageInt; -import mage.MageObjectReference; -import mage.abilities.Ability; import mage.abilities.common.DiesSourceTriggeredAbility; import mage.abilities.common.EntersBattlefieldAbility; import mage.abilities.condition.InvertCondition; import mage.abilities.condition.common.CastFromHandSourcePermanentCondition; -import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.continuous.GainSuspendEffect; +import mage.abilities.effects.common.ExileSpellWithTimeCountersEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; -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.Zone; import mage.counters.CounterType; -import mage.game.Game; -import mage.players.Player; import mage.watchers.common.CastFromHandWatcher; import java.util.UUID; @@ -45,7 +36,10 @@ public final class Epochrasite extends CardImpl { new CastFromHandWatcher()); // When Epochrasite dies, exile it with three time counters on it and it gains suspend. - this.addAbility(new DiesSourceTriggeredAbility(new EpochrasiteEffect())); + this.addAbility(new DiesSourceTriggeredAbility(new ExileSpellWithTimeCountersEffect(3, true) + .setText("exile it with three time counters on it and it gains suspend." + + " (At the beginning of its owner's upkeep, they remove a time counter." + + " When the last is removed, they may cast this card without paying its mana cost. It has haste.)"))); } private Epochrasite(final Epochrasite card) { @@ -57,41 +51,3 @@ public final class Epochrasite extends CardImpl { return new Epochrasite(this); } } - -class EpochrasiteEffect extends OneShotEffect { - - EpochrasiteEffect() { - super(Outcome.Benefit); - this.staticText = "exile it with three time counters on it and it gains suspend"; - } - - private EpochrasiteEffect(final EpochrasiteEffect effect) { - super(effect); - } - - @Override - public EpochrasiteEffect copy() { - return new EpochrasiteEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - Card card = game.getCard(source.getSourceId()); - if (controller == null || card == null) { - return false; - } - card = card.getMainCard(); - - if (game.getState().getZone(card.getId()) != Zone.GRAVEYARD) { - return false; - } - - UUID exileId = SuspendAbility.getSuspendExileId(controller.getId(), game); - controller.moveCardToExileWithInfo(card, exileId, "Suspended cards of " + controller.getName(), source, game, Zone.GRAVEYARD, true); - card.addCounters(CounterType.TIME.createInstance(3), source.getControllerId(), source, game); - game.addEffect(new GainSuspendEffect(new MageObjectReference(card, game)), source); - - return true; - } -} diff --git a/Mage.Sets/src/mage/cards/e/ErietteTheBeguiler.java b/Mage.Sets/src/mage/cards/e/ErietteTheBeguiler.java new file mode 100644 index 00000000000..d3c8e952ccb --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/ErietteTheBeguiler.java @@ -0,0 +1,121 @@ +package mage.cards.e; + +import mage.MageInt; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.common.continuous.GainControlTargetEffect; +import mage.abilities.keyword.LifelinkAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ErietteTheBeguiler extends CardImpl { + + public ErietteTheBeguiler(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}{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(4); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // Whenever an Aura you control becomes attached to a nonland permanent an opponent controls with mana value less than or equal to that Aura's mana value, gain control of that permanent for as long as that Aura is attached to it. + this.addAbility(new ErietteTheBeguilerTriggeredAbility()); + } + + private ErietteTheBeguiler(final ErietteTheBeguiler card) { + super(card); + } + + @Override + public ErietteTheBeguiler copy() { + return new ErietteTheBeguiler(this); + } +} + +class ErietteTheBeguilerTriggeredAbility extends TriggeredAbilityImpl { + + ErietteTheBeguilerTriggeredAbility() { + super(Zone.BATTLEFIELD, new ErietteTheBeguilerEffect()); + } + + private ErietteTheBeguilerTriggeredAbility(final ErietteTheBeguilerTriggeredAbility ability) { + super(ability); + } + + @Override + public ErietteTheBeguilerTriggeredAbility copy() { + return new ErietteTheBeguilerTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ATTACHED; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + Permanent aura = game.getPermanent(event.getSourceId()); + Permanent permanent = game.getPermanent(event.getTargetId()); + if (aura == null + || permanent == null + || !aura.isControlledBy(getControllerId()) + || !aura.hasSubtype(SubType.AURA, game) + || permanent.isLand(game) + || !game.getOpponents(getControllerId()).contains(permanent.getControllerId()) + || aura.getManaValue() < permanent.getManaValue()) { + return false; + } + this.getEffects().setValue("auraRef", new MageObjectReference(aura, game)); + this.getEffects().setTargetPointer(new FixedTarget(permanent, game)); + return true; + } + + @Override + public String getRule() { + return "Whenever an Aura you control becomes attached to a nonland permanent " + + "an opponent controls with mana value less than or equal to that Aura's mana value, " + + "gain control of that permanent for as long as that Aura is attached to it."; + } +} + +class ErietteTheBeguilerEffect extends GainControlTargetEffect { + + ErietteTheBeguilerEffect() { + super(Duration.Custom); + } + + private ErietteTheBeguilerEffect(final ErietteTheBeguilerEffect effect) { + super(effect); + } + + @Override + public ErietteTheBeguilerEffect copy() { + return new ErietteTheBeguilerEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent aura = ((MageObjectReference) getValue("auraRef")).getPermanent(game); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (aura == null || permanent == null || !permanent.getId().equals(aura.getAttachedTo())) { + discard(); + return false; + } + return super.apply(game, source); + } +} diff --git a/Mage.Sets/src/mage/cards/e/EriettesLullaby.java b/Mage.Sets/src/mage/cards/e/EriettesLullaby.java new file mode 100644 index 00000000000..09427d22399 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EriettesLullaby.java @@ -0,0 +1,43 @@ +package mage.cards.e; + +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.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.TappedPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EriettesLullaby extends CardImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent("tapped creature"); + + static { + filter.add(TappedPredicate.TAPPED); + } + + public EriettesLullaby(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{W}"); + + // Destroy target tapped creature. You gain 2 life. + this.getSpellAbility().addEffect(new DestroyTargetEffect()); + this.getSpellAbility().addTarget(new TargetPermanent(filter)); + this.getSpellAbility().addEffect(new GainLifeEffect(2)); + } + + private EriettesLullaby(final EriettesLullaby card) { + super(card); + } + + @Override + public EriettesLullaby copy() { + return new EriettesLullaby(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/ErisRoarOfTheStorm.java b/Mage.Sets/src/mage/cards/e/ErisRoarOfTheStorm.java new file mode 100644 index 00000000000..1f9e8631eb1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/ErisRoarOfTheStorm.java @@ -0,0 +1,134 @@ +package mage.cards.e; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.CastSecondSpellTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; +import mage.abilities.hint.Hint; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.ProwessAbility; +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.game.Game; +import mage.game.permanent.token.DragonElementalToken; +import mage.players.Player; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * @author TheElk801 + */ +public final class ErisRoarOfTheStorm extends CardImpl { + + public ErisRoarOfTheStorm(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{8}{U}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.ELEMENTAL); + this.subtype.add(SubType.WARLOCK); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // This spell costs {2} less to cast for each different mana value among instant and sorcery cards in your graveyard. + this.addAbility(new SimpleStaticAbility( + Zone.ALL, + new SpellCostReductionForEachSourceEffect( + 2, ErisRoarOfTheStormValue.instance + ).setCanWorksOnStackOnly(true) + ).setRuleAtTheTop(true).addHint(ErisRoarOfTheStormHint.instance)); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Prowess + this.addAbility(new ProwessAbility()); + + // Whenever you cast your second spell each turn, create a 4/4 red Dragon Elemental creature token with flying and prowess. + this.addAbility(new CastSecondSpellTriggeredAbility(new CreateTokenEffect(new DragonElementalToken()))); + } + + private ErisRoarOfTheStorm(final ErisRoarOfTheStorm card) { + super(card); + } + + @Override + public ErisRoarOfTheStorm copy() { + return new ErisRoarOfTheStorm(this); + } +} + +enum ErisRoarOfTheStormValue implements DynamicValue { + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return makeStream(game, sourceAbility) + .mapToInt(x -> 1) + .sum(); + } + + static Stream makeStream(Game game, Ability source) { + return Optional + .ofNullable(game.getPlayer(source.getControllerId())) + .map(Player::getGraveyard) + .map(g -> g.getCards(StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY, game)) + .map(Collection::stream) + .map(s -> s.map(MageObject::getManaValue)) + .map(Stream::distinct) + .orElseGet(Stream::empty); + } + + @Override + public ErisRoarOfTheStormValue copy() { + return this; + } + + @Override + public String getMessage() { + return "different mana value among instant and sorcery cards in your graveyard"; + } + + @Override + public String toString() { + return "1"; + } +} + +enum ErisRoarOfTheStormHint implements Hint { + instance; + + @Override + public String getText(Game game, Ability ability) { + Player player = game.getPlayer(ability.getControllerId()); + if (player == null) { + return null; + } + List values = ErisRoarOfTheStormValue + .makeStream(game, ability) + .sorted() + .map(String::valueOf) + .collect(Collectors.toList()); + return "Different mana values among instants and sorceries in your graveyard: " + values.size() + + (values.size() > 0 ? " (" + String.join(", ", values) + ')' : ""); + } + + @Override + public Hint copy() { + return this; + } +} diff --git a/Mage.Sets/src/mage/cards/e/ErodedCanyon.java b/Mage.Sets/src/mage/cards/e/ErodedCanyon.java new file mode 100644 index 00000000000..2bf440614e2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/ErodedCanyon.java @@ -0,0 +1,48 @@ +package mage.cards.e; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTappedAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.mana.BlueManaAbility; +import mage.abilities.mana.RedManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ErodedCanyon extends CardImpl { + + public ErodedCanyon(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + this.subtype.add(SubType.DESERT); + + // Eroded Canyon enters the battlefield tapped. + this.addAbility(new EntersBattlefieldTappedAbility()); + + // When Eroded Canyon enters the battlefield, it deals 1 damage to target opponent. + Ability ability = new EntersBattlefieldTriggeredAbility(new DamageTargetEffect(1, "it")); + ability.addTarget(new TargetOpponent()); + this.addAbility(ability); + + // {T}: Add {U} or {R}. + this.addAbility(new BlueManaAbility()); + this.addAbility(new RedManaAbility()); + } + + private ErodedCanyon(final ErodedCanyon card) { + super(card); + } + + @Override + public ErodedCanyon copy() { + return new ErodedCanyon(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/ErrantAndGiada.java b/Mage.Sets/src/mage/cards/e/ErrantAndGiada.java index a2d410626e6..441dd9d1125 100644 --- a/Mage.Sets/src/mage/cards/e/ErrantAndGiada.java +++ b/Mage.Sets/src/mage/cards/e/ErrantAndGiada.java @@ -3,7 +3,7 @@ package mage.cards.e; import mage.MageInt; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; -import mage.abilities.effects.common.continuous.PlayTheTopCardEffect; +import mage.abilities.effects.common.continuous.PlayFromTopOfLibraryEffect; import mage.abilities.keyword.FlashAbility; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; @@ -51,7 +51,7 @@ public final class ErrantAndGiada extends CardImpl { this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); // You may cast spells with flash or flying from the top of your library. - this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(TargetController.YOU, filter, false))); + this.addAbility(new SimpleStaticAbility(new PlayFromTopOfLibraryEffect(filter))); } private ErrantAndGiada(final ErrantAndGiada card) { diff --git a/Mage.Sets/src/mage/cards/e/ErthaJoFrontierMentor.java b/Mage.Sets/src/mage/cards/e/ErthaJoFrontierMentor.java new file mode 100644 index 00000000000..cb1df4774ce --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/ErthaJoFrontierMentor.java @@ -0,0 +1,100 @@ +package mage.cards.e; + +import mage.MageInt; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.CopyStackObjectEffect; +import mage.abilities.effects.common.CreateTokenEffect; +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.FilterPlayer; +import mage.filter.FilterStackObject; +import mage.filter.StaticFilters; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.filter.predicate.mageobject.TargetsPermanentOrPlayerPredicate; +import mage.filter.predicate.mageobject.TargetsPermanentPredicate; +import mage.filter.predicate.mageobject.TargetsPlayerPredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.token.MercenaryToken; +import mage.game.stack.StackObject; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class ErthaJoFrontierMentor extends CardImpl { + + public ErthaJoFrontierMentor(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.KOR); + this.subtype.add(SubType.ADVISOR); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // When Ertha Jo, Frontier Mentor enters the battlefield, create a 1/1 red Mercenary creature token with "{T}: Target creature you control gets +1/+0 until end of turn. Activate only as a sorcery." + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new MercenaryToken()))); + + // Whenever you activate an ability that targets a creature or player, copy that ability. You may choose new targets for the copy. + this.addAbility(new ErthaJoFrontierMentorTriggeredAbility()); + } + + private ErthaJoFrontierMentor(final ErthaJoFrontierMentor card) { + super(card); + } + + @Override + public ErthaJoFrontierMentor copy() { + return new ErthaJoFrontierMentor(this); + } +} + +class ErthaJoFrontierMentorTriggeredAbility extends TriggeredAbilityImpl { + + private static final FilterStackObject filter = new FilterStackObject("ability that targets a creature or player"); + + static { + filter.add(new TargetsPermanentOrPlayerPredicate(StaticFilters.FILTER_PERMANENT_CREATURE, new FilterPlayer())); + } + + public ErthaJoFrontierMentorTriggeredAbility() { + super(Zone.BATTLEFIELD, new CopyStackObjectEffect(), false); + setTriggerPhrase("Whenever you activate an ability that targets a creature or player, "); + } + + private ErthaJoFrontierMentorTriggeredAbility(final ErthaJoFrontierMentorTriggeredAbility ability) { + super(ability); + } + + @Override + public ErthaJoFrontierMentorTriggeredAbility copy() { + return new ErthaJoFrontierMentorTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ACTIVATED_ABILITY; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + StackObject stackObject = game.getStack().getStackObject(event.getSourceId()); + if (stackObject == null + || !event.getPlayerId().equals(getControllerId()) + || !filter.match(stackObject, getControllerId(), this, game) + ) { + return false; + } + // For the copy effect to find. + this.getEffects().setValue("stackObject", stackObject); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/e/EsotericDuplicator.java b/Mage.Sets/src/mage/cards/e/EsotericDuplicator.java new file mode 100644 index 00000000000..cbd19934014 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EsotericDuplicator.java @@ -0,0 +1,82 @@ +package mage.cards.e; + +import mage.abilities.Ability; +import mage.abilities.common.SacrificePermanentTriggeredAbility; +import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenCopyTargetEffect; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.token.ClueAbility; +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.FilterPermanentThisOrAnother; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EsotericDuplicator extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanentThisOrAnother( + StaticFilters.FILTER_PERMANENT_ARTIFACT, + true, "{this} or another artifact" + ); + + public EsotericDuplicator(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{U}"); + + this.subtype.add(SubType.CLUE); + + // Whenever you sacrifice Esoteric Duplicator or another artifact, you may pay {2}. If you do, at the beginning of the next end step, create a token that's a copy of that artifact. + this.addAbility(new SacrificePermanentTriggeredAbility( + new DoIfCostPaid(new EsotericDuplicatorEffect(), new GenericManaCost(2)), filter + )); + + // {2}, Sacrifice Esoteric Duplicator: Draw a card. + this.addAbility(new ClueAbility(true)); + } + + private EsotericDuplicator(final EsotericDuplicator card) { + super(card); + } + + @Override + public EsotericDuplicator copy() { + return new EsotericDuplicator(this); + } +} + +class EsotericDuplicatorEffect extends OneShotEffect { + + EsotericDuplicatorEffect() { + super(Outcome.Benefit); + staticText = "at the beginning of the next end step, create a token that's a copy of that artifact"; + } + + private EsotericDuplicatorEffect(final EsotericDuplicatorEffect effect) { + super(effect); + } + + @Override + public EsotericDuplicatorEffect copy() { + return new EsotericDuplicatorEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = (Permanent) getValue("sacrificedPermanent"); + game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility( + new CreateTokenCopyTargetEffect().setSavedPermanent(permanent) + ), source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/e/EtheriumSculptor.java b/Mage.Sets/src/mage/cards/e/EtheriumSculptor.java index 598231fe3a3..e8a37bcc953 100644 --- a/Mage.Sets/src/mage/cards/e/EtheriumSculptor.java +++ b/Mage.Sets/src/mage/cards/e/EtheriumSculptor.java @@ -1,7 +1,5 @@ - package mage.cards.e; -import java.util.UUID; import mage.MageInt; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.cost.SpellsCostReductionControllerEffect; @@ -9,21 +7,20 @@ 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.common.FilterArtifactCard; + +import java.util.UUID; /** - * * @author North */ public final class EtheriumSculptor extends CardImpl { - private static final FilterCard filter = new FilterCard("Artifact spells"); - static { - filter.add(CardType.ARTIFACT.getPredicate()); - } + + private static final FilterCard filter = new FilterArtifactCard("artifact spells"); public EtheriumSculptor(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT,CardType.CREATURE},"{1}{U}"); + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{1}{U}"); this.subtype.add(SubType.VEDALKEN); this.subtype.add(SubType.ARTIFICER); @@ -31,7 +28,7 @@ public final class EtheriumSculptor extends CardImpl { this.toughness = new MageInt(2); // Artifact spells you cast cost {1} less to cast. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new SpellsCostReductionControllerEffect(filter, 1))); + this.addAbility(new SimpleStaticAbility(new SpellsCostReductionControllerEffect(filter, 1))); } private EtheriumSculptor(final EtheriumSculptor card) { diff --git a/Mage.Sets/src/mage/cards/e/EverythingComesToDust.java b/Mage.Sets/src/mage/cards/e/EverythingComesToDust.java new file mode 100644 index 00000000000..85e501262a3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EverythingComesToDust.java @@ -0,0 +1,70 @@ +package mage.cards.e; + +import mage.MageObjectReference; +import mage.abilities.effects.common.ExileAllEffect; +import mage.abilities.keyword.ConvokeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.FilterPermanent; +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.HashSet; +import java.util.UUID; + +/** + * + * @author notgreat + */ +public final class EverythingComesToDust extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("creatures except those that share a creature type with a creature that convoked this spell, all artifacts, and all enchantments"); + + static { + filter.add(EverythingComesToDustPredicate.instance); + } + public EverythingComesToDust(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{7}{W}{W}{W}"); + + // Convoke + this.addAbility(new ConvokeAbility()); + + // Exile all creatures except those that share a creature type with a creature that convoked this spell, all artifacts, and all enchantments. + this.getSpellAbility().addEffect(new ExileAllEffect(filter)); + } + + private EverythingComesToDust(final EverythingComesToDust card) { + super(card); + } + + @Override + public EverythingComesToDust copy() { + return new EverythingComesToDust(this); + } +} + +enum EverythingComesToDustPredicate implements ObjectSourcePlayerPredicate { + instance; + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + Permanent p = input.getObject(); + if (p.isArtifact(game) || p.isEnchantment(game)){ + return true; + } + if (!p.isCreature(game)){ + return false; + } + HashSet set = CardUtil.getSourceCostsTag(game, input.getSource(), ConvokeAbility.convokingCreaturesKey, new HashSet<>(0)); + for (MageObjectReference mor : set){ + Permanent convoked = game.getPermanentOrLKIBattlefield(mor); + if (convoked.shareCreatureTypes(game, p)){ + return false; + } + } + return true; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/e/ExpeditedInheritance.java b/Mage.Sets/src/mage/cards/e/ExpeditedInheritance.java index 38a2f905363..9a3a5351604 100644 --- a/Mage.Sets/src/mage/cards/e/ExpeditedInheritance.java +++ b/Mage.Sets/src/mage/cards/e/ExpeditedInheritance.java @@ -58,7 +58,7 @@ class ExpeditedInheritanceTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT; } @Override diff --git a/Mage.Sets/src/mage/cards/e/ExperimentalFrenzy.java b/Mage.Sets/src/mage/cards/e/ExperimentalFrenzy.java index bfe6f2c1586..b962100625f 100644 --- a/Mage.Sets/src/mage/cards/e/ExperimentalFrenzy.java +++ b/Mage.Sets/src/mage/cards/e/ExperimentalFrenzy.java @@ -7,7 +7,7 @@ import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; import mage.abilities.effects.common.DestroySourceEffect; import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; -import mage.abilities.effects.common.continuous.PlayTheTopCardEffect; +import mage.abilities.effects.common.continuous.PlayFromTopOfLibraryEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -31,7 +31,7 @@ public final class ExperimentalFrenzy extends CardImpl { this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); // You may play lands and cast spells from the top of your library. - this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect())); + this.addAbility(new SimpleStaticAbility(new PlayFromTopOfLibraryEffect())); // You can't play cards from your hand. this.addAbility(new SimpleStaticAbility(new ExperimentalFrenzyRestrictionEffect())); diff --git a/Mage.Sets/src/mage/cards/e/ExplosiveDerailment.java b/Mage.Sets/src/mage/cards/e/ExplosiveDerailment.java new file mode 100644 index 00000000000..1bd815956a3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/ExplosiveDerailment.java @@ -0,0 +1,46 @@ +package mage.cards.e; + +import mage.abilities.Mode; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.keyword.SpreeAbility; +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 ExplosiveDerailment extends CardImpl { + + public ExplosiveDerailment(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{R}"); + + // Spree + this.addAbility(new SpreeAbility(this)); + + // + {2} -- Explosive Derailment deals 4 damage to target creature. + this.getSpellAbility().addEffect(new DamageTargetEffect(4)); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + this.getSpellAbility().withFirstModeCost(new GenericManaCost(2)); + + // + {2} -- Destroy target artifact. + this.getSpellAbility().addMode(new Mode(new DestroyTargetEffect()) + .addTarget(new TargetArtifactPermanent()) + .withCost(new GenericManaCost(2))); + } + + private ExplosiveDerailment(final ExplosiveDerailment card) { + super(card); + } + + @Override + public ExplosiveDerailment copy() { + return new ExplosiveDerailment(this); + } +} diff --git a/Mage.Sets/src/mage/cards/e/ExtractAConfession.java b/Mage.Sets/src/mage/cards/e/ExtractAConfession.java new file mode 100644 index 00000000000..b4870f22ddb --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/ExtractAConfession.java @@ -0,0 +1,53 @@ +package mage.cards.e; + +import java.util.UUID; + +import mage.abilities.condition.common.CollectedEvidenceCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.SacrificeOpponentsEffect; +import mage.abilities.keyword.CollectEvidenceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.permanent.GreatestPowerControlledPredicate; + +/** + * @author Cguy7777 + */ +public final class ExtractAConfession extends CardImpl { + + private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent( + "a creature with the greatest power among creatures they control"); + + static { + filter.add(GreatestPowerControlledPredicate.instance); + } + + public ExtractAConfession(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{B}"); + + // As an additional cost to cast this spell, you may collect evidence 6. + this.addAbility(new CollectEvidenceAbility(6)); + + // Each opponent sacrifices a creature. If evidence was collected, + // instead each opponent sacrifices a creature with the greatest power among creatures they control. + this.getSpellAbility().addEffect(new ConditionalOneShotEffect( + new SacrificeOpponentsEffect(filter), + new SacrificeOpponentsEffect(StaticFilters.FILTER_CONTROLLED_CREATURE_SHORT_TEXT), + CollectedEvidenceCondition.instance, + "each opponent sacrifices a creature. " + + "If evidence was collected, instead each opponent " + + "sacrifices a creature with the greatest power among creatures they control")); + } + + private ExtractAConfession(final ExtractAConfession card) { + super(card); + } + + @Override + public ExtractAConfession copy() { + return new ExtractAConfession(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FailedFording.java b/Mage.Sets/src/mage/cards/f/FailedFording.java new file mode 100644 index 00000000000..73643bde33a --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FailedFording.java @@ -0,0 +1,49 @@ +package mage.cards.f; + +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.ReturnToHandTargetEffect; +import mage.abilities.effects.keyword.SurveilEffect; +import mage.abilities.hint.ConditionHint; +import mage.abilities.hint.Hint; +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.TargetNonlandPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FailedFording extends CardImpl { + + private static final Condition condition + = new PermanentsOnTheBattlefieldCondition(new FilterControlledPermanent(SubType.DESERT)); + private static final Hint hint = new ConditionHint(condition, "You control a Desert"); + + public FailedFording(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}"); + + // Return target nonland permanent to its owner's hand. If you control a Desert, surveil 1. + this.getSpellAbility().addEffect(new ReturnToHandTargetEffect()); + this.getSpellAbility().addTarget(new TargetNonlandPermanent()); + this.getSpellAbility().addEffect(new ConditionalOneShotEffect( + new SurveilEffect(1), condition, + "if you control a Desert, surveil 1" + )); + this.getSpellAbility().addHint(hint); + } + + private FailedFording(final FailedFording card) { + super(card); + } + + @Override + public FailedFording copy() { + return new FailedFording(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FallOfCairAndros.java b/Mage.Sets/src/mage/cards/f/FallOfCairAndros.java index 83646f7a6ed..26d3c005deb 100644 --- a/Mage.Sets/src/mage/cards/f/FallOfCairAndros.java +++ b/Mage.Sets/src/mage/cards/f/FallOfCairAndros.java @@ -12,6 +12,8 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; import mage.game.Game; +import mage.game.events.DamagedBatchForOnePermanentEvent; +import mage.game.events.DamagedEvent; import mage.game.events.DamagedPermanentEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; @@ -63,7 +65,7 @@ class FallOfCairAndrosTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT; } @Override @@ -73,12 +75,17 @@ class FallOfCairAndrosTriggeredAbility extends TriggeredAbilityImpl { || !game.getOpponents(getControllerId()).contains(permanent.getControllerId())) { return false; } - DamagedPermanentEvent dEvent = (DamagedPermanentEvent) event; - if (dEvent.isCombatDamage() || dEvent.getExcess() < 1) { + DamagedBatchForOnePermanentEvent dEvent = (DamagedBatchForOnePermanentEvent) event; + int excessDamage = dEvent.getEvents() + .stream() + .mapToInt(DamagedEvent::getExcess) + .sum(); + + if (dEvent.isCombatDamage() || excessDamage < 1) { return false; } this.getEffects().clear(); - this.addEffect(new AmassEffect(dEvent.getExcess(), SubType.ORC)); + this.addEffect(new AmassEffect(excessDamage, SubType.ORC)); return true; } diff --git a/Mage.Sets/src/mage/cards/f/FblthpLostOnTheRange.java b/Mage.Sets/src/mage/cards/f/FblthpLostOnTheRange.java new file mode 100644 index 00000000000..a5300120290 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FblthpLostOnTheRange.java @@ -0,0 +1,116 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; +import mage.abilities.keyword.PlotAbility; +import mage.abilities.keyword.WardAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.players.Player; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class FblthpLostOnTheRange extends CardImpl { + + public FblthpLostOnTheRange(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HOMUNCULUS); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Ward {2} + this.addAbility(new WardAbility(new ManaCostsImpl<>("{2}"))); + + // You may look at the top card of your library any time. + this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); + + // The top card of your library has plot. The plot cost is equal to its mana cost. + this.addAbility(new SimpleStaticAbility(new FblthpLostOnTheRangePlotGivingEffect())); + + // You may plot nonland cards from the top of your library. + this.addAbility(new SimpleStaticAbility(new FblthpLostOnTheRangePermissionEffect())); + } + + private FblthpLostOnTheRange(final FblthpLostOnTheRange card) { + super(card); + } + + @Override + public FblthpLostOnTheRange copy() { + return new FblthpLostOnTheRange(this); + } +} + +class FblthpLostOnTheRangePlotGivingEffect extends ContinuousEffectImpl { + + FblthpLostOnTheRangePlotGivingEffect() { + super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); + this.staticText = "The top card of your library has plot. The plot cost is equal to its mana cost."; + } + + private FblthpLostOnTheRangePlotGivingEffect(final FblthpLostOnTheRangePlotGivingEffect effect) { + super(effect); + } + + @Override + public FblthpLostOnTheRangePlotGivingEffect copy() { + return new FblthpLostOnTheRangePlotGivingEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + Card card = controller.getLibrary().getFromTop(game); + if (card == null) { + return false; + } + game.getState().addOtherAbility(card, new PlotAbility(card.getManaCost().getText())); + return true; + } +} + +class FblthpLostOnTheRangePermissionEffect extends ContinuousEffectImpl { + + FblthpLostOnTheRangePermissionEffect() { + this(Duration.WhileOnBattlefield); + } + + public FblthpLostOnTheRangePermissionEffect(Duration duration) { + super(duration, Layer.PlayerEffects, SubLayer.NA, Outcome.Benefit); + staticText = "You may plot nonland cards from the top of your library"; + } + + private FblthpLostOnTheRangePermissionEffect(final FblthpLostOnTheRangePermissionEffect effect) { + super(effect); + } + + @Override + public FblthpLostOnTheRangePermissionEffect copy() { + return new FblthpLostOnTheRangePermissionEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + controller.setPlotFromTopOfLibrary(true); + return true; + } + return false; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/f/FelineSovereign.java b/Mage.Sets/src/mage/cards/f/FelineSovereign.java index 0eca44ca7f3..a2201d40efa 100644 --- a/Mage.Sets/src/mage/cards/f/FelineSovereign.java +++ b/Mage.Sets/src/mage/cards/f/FelineSovereign.java @@ -89,7 +89,7 @@ class FelineSovereignTriggeredAbility extends DealCombatDamageControlledTriggere } this.getTargets().clear(); FilterArtifactOrEnchantmentPermanent filter = new FilterArtifactOrEnchantmentPermanent(); - filter.add(new ControllerIdPredicate(event.getPlayerId())); + filter.add(new ControllerIdPredicate(event.getTargetId())); this.addTarget(new TargetPermanent(0, 1, filter, false)); return true; } diff --git a/Mage.Sets/src/mage/cards/f/Ferocification.java b/Mage.Sets/src/mage/cards/f/Ferocification.java new file mode 100644 index 00000000000..72cbdb1c80c --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/Ferocification.java @@ -0,0 +1,48 @@ +package mage.cards.f; + +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.common.BeginningOfCombatTriggeredAbility; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.TargetController; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Ferocification extends CardImpl { + + public Ferocification(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{R}"); + + // At the beginning of combat on your turn, choose one-- + // * Target creature you control gets +2/+0 until end of turn. + Ability ability = new BeginningOfCombatTriggeredAbility( + new BoostTargetEffect(2, 0), TargetController.YOU, false + ); + ability.addTarget(new TargetControlledCreaturePermanent()); + + // * Target creature you control gains menace and haste until end of turn. + ability.addMode(new Mode(new GainAbilityTargetEffect(new MenaceAbility()).setText("target creature you control gains menace")) + .addEffect(new GainAbilityTargetEffect(HasteAbility.getInstance()).setText("and haste until end of turn")) + .addTarget(new TargetControlledCreaturePermanent())); + this.addAbility(ability); + } + + private Ferocification(final Ferocification card) { + super(card); + } + + @Override + public Ferocification copy() { + return new Ferocification(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FesteringGulch.java b/Mage.Sets/src/mage/cards/f/FesteringGulch.java new file mode 100644 index 00000000000..cafe17967bb --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FesteringGulch.java @@ -0,0 +1,48 @@ +package mage.cards.f; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTappedAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.mana.BlackManaAbility; +import mage.abilities.mana.GreenManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FesteringGulch extends CardImpl { + + public FesteringGulch(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + this.subtype.add(SubType.DESERT); + + // Festering Gulch enters the battlefield tapped. + this.addAbility(new EntersBattlefieldTappedAbility()); + + // When Festering Gulch enters the battlefield, it deals 1 damage to target opponent. + Ability ability = new EntersBattlefieldTriggeredAbility(new DamageTargetEffect(1, "it")); + ability.addTarget(new TargetOpponent()); + this.addAbility(ability); + + // {T}: Add {B} or {G}. + this.addAbility(new BlackManaAbility()); + this.addAbility(new GreenManaAbility()); + } + + private FesteringGulch(final FesteringGulch card) { + super(card); + } + + @Override + public FesteringGulch copy() { + return new FesteringGulch(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FilthyCur.java b/Mage.Sets/src/mage/cards/f/FilthyCur.java index 4b836797234..d983778da5d 100644 --- a/Mage.Sets/src/mage/cards/f/FilthyCur.java +++ b/Mage.Sets/src/mage/cards/f/FilthyCur.java @@ -60,7 +60,7 @@ class DealtDamageLoseLifeTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT; } @Override diff --git a/Mage.Sets/src/mage/cards/f/FinalShowdown.java b/Mage.Sets/src/mage/cards/f/FinalShowdown.java new file mode 100644 index 00000000000..7cc0142b8ac --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FinalShowdown.java @@ -0,0 +1,94 @@ +package mage.cards.f; + +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DestroyAllEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.effects.common.continuous.LoseAllAbilitiesAllEffect; +import mage.abilities.keyword.IndestructibleAbility; +import mage.abilities.keyword.SpreeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +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 mage.target.TargetPermanent; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FinalShowdown extends CardImpl { + + public FinalShowdown(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{W}"); + + // Spree + this.addAbility(new SpreeAbility(this)); + + // + {1} -- All creatures lose all abilities until end of turn. + this.getSpellAbility().addEffect(new LoseAllAbilitiesAllEffect( + StaticFilters.FILTER_PERMANENT_CREATURES, Duration.EndOfTurn + ).setText("all creatures lose all abilities until end of turn")); + this.getSpellAbility().withFirstModeCost(new GenericManaCost(1)); + + // + {1} -- Choose a creature you control. It gains indestructible until end of turn. + this.getSpellAbility().addMode(new Mode(new FinalShowdownEffect()).withCost(new GenericManaCost(1))); + + // + {3}{W}{W} -- Destroy all creatures. + this.getSpellAbility().addMode(new Mode(new DestroyAllEffect(StaticFilters.FILTER_PERMANENT_CREATURES)) + .withCost(new ManaCostsImpl<>("{3}{W}{W}"))); + } + + private FinalShowdown(final FinalShowdown card) { + super(card); + } + + @Override + public FinalShowdown copy() { + return new FinalShowdown(this); + } +} + +class FinalShowdownEffect extends OneShotEffect { + + FinalShowdownEffect() { + super(Outcome.Benefit); + staticText = "choose a creature you control. It gains indestructible until end of turn"; + } + + private FinalShowdownEffect(final FinalShowdownEffect effect) { + super(effect); + } + + @Override + public FinalShowdownEffect copy() { + return new FinalShowdownEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null || !game.getBattlefield().contains( + StaticFilters.FILTER_CONTROLLED_CREATURE, source, game, 1 + )) { + return false; + } + TargetPermanent target = new TargetControlledCreaturePermanent(); + target.withNotTarget(true); + player.choose(outcome, target, source, game); + game.addEffect(new GainAbilityTargetEffect( + IndestructibleAbility.getInstance(), Duration.EndOfTurn + ).setTargetPointer(new FixedTarget(target.getFirstTarget(), game)), source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/f/FinaleOfPromise.java b/Mage.Sets/src/mage/cards/f/FinaleOfPromise.java index e14d072a3cd..9ee7b94bf0f 100644 --- a/Mage.Sets/src/mage/cards/f/FinaleOfPromise.java +++ b/Mage.Sets/src/mage/cards/f/FinaleOfPromise.java @@ -3,11 +3,12 @@ package mage.cards.f; import mage.abilities.Ability; import mage.abilities.dynamicvalue.common.ManacostVariableValue; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.MayCastTargetThenExileEffect; +import mage.abilities.effects.common.MayCastTargetCardEffect; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.CastManaAdjustment; import mage.constants.ComparisonType; import mage.constants.Outcome; import mage.filter.FilterCard; @@ -133,7 +134,8 @@ class FinaleOfPromiseEffect extends OneShotEffect { for (UUID id : cardsToCast) { Card card = game.getCard(id); if (card != null) { - new MayCastTargetThenExileEffect(true).setTargetPointer(new FixedTarget(card, game)).apply(game, source); + new MayCastTargetCardEffect(CastManaAdjustment.WITHOUT_PAYING_MANA_COST, true) + .setTargetPointer(new FixedTarget(card, game)).apply(game, source); } } diff --git a/Mage.Sets/src/mage/cards/f/FleetingReflection.java b/Mage.Sets/src/mage/cards/f/FleetingReflection.java new file mode 100644 index 00000000000..6f0fa97ed61 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FleetingReflection.java @@ -0,0 +1,81 @@ +package mage.cards.f; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.UntapTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.HexproofAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.other.AnotherTargetPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetCreaturePermanent; +import mage.util.functions.EmptyCopyApplier; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class FleetingReflection extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("other target creature"); + + static { + filter.add(new AnotherTargetPredicate(2)); + } + + public FleetingReflection(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}"); + + // Target creature you control gains hexproof until end of turn. Untap that creature. Until end of turn, it becomes a copy of up to one other target creature. + this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent().setTargetTag(1)); + this.getSpellAbility().addTarget(new TargetCreaturePermanent(0, 1, filter, false).setTargetTag(2)); + this.getSpellAbility().addEffect(new GainAbilityTargetEffect(HexproofAbility.getInstance(), Duration.EndOfTurn)); + this.getSpellAbility().addEffect(new UntapTargetEffect().setText("Untap that creature")); + this.getSpellAbility().addEffect(new FleetingReflectionEffect()); + } + + private FleetingReflection(final FleetingReflection card) { + super(card); + } + + @Override + public FleetingReflection copy() { + return new FleetingReflection(this); + } +} + +class FleetingReflectionEffect extends OneShotEffect { + + FleetingReflectionEffect() { + super(Outcome.Neutral); + staticText = "Until end of turn, it becomes a copy of up to one other target creature"; + } + + private FleetingReflectionEffect(final FleetingReflectionEffect effect) { + super(effect); + } + + @Override + public FleetingReflectionEffect copy() { + return new FleetingReflectionEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent copyTo = game.getPermanent(getTargetPointer().getFirst(game, source)); + Permanent copyFrom = game.getPermanentOrLKIBattlefield(source.getTargets().get(1).getFirstTarget()); + if (copyTo == null || copyFrom == null) { + return false; + } + game.copyPermanent(Duration.EndOfTurn, copyFrom, copyTo.getId(), source, new EmptyCopyApplier()); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/f/FomoriVault.java b/Mage.Sets/src/mage/cards/f/FomoriVault.java new file mode 100644 index 00000000000..fa1fdbc171c --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FomoriVault.java @@ -0,0 +1,47 @@ +package mage.cards.f; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.DiscardCardCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.dynamicvalue.common.ArtifactYouControlCount; +import mage.abilities.effects.common.LookLibraryAndPickControllerEffect; +import mage.abilities.hint.common.ArtifactYouControlHint; +import mage.abilities.mana.ColorlessManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.PutCards; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FomoriVault extends CardImpl { + + public FomoriVault(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + // {T}: Add {1}. + this.addAbility(new ColorlessManaAbility()); + + // {3}, {T}, Discard a card: Look at the top X cards of your library, where X is the number of artifacts you control. Put one of those cards into your hand and the rest on the bottom of your library in random order. + Ability ability = new SimpleActivatedAbility(new LookLibraryAndPickControllerEffect( + ArtifactYouControlCount.instance, 1, PutCards.HAND, PutCards.BOTTOM_RANDOM + ), new GenericManaCost(3)); + ability.addCost(new TapSourceCost()); + ability.addCost(new DiscardCardCost()); + this.addAbility(ability.addHint(ArtifactYouControlHint.instance)); + } + + private FomoriVault(final FomoriVault card) { + super(card); + } + + @Override + public FomoriVault copy() { + return new FomoriVault(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/ForlornFlats.java b/Mage.Sets/src/mage/cards/f/ForlornFlats.java new file mode 100644 index 00000000000..ffbbc798c05 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/ForlornFlats.java @@ -0,0 +1,48 @@ +package mage.cards.f; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTappedAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.mana.BlackManaAbility; +import mage.abilities.mana.WhiteManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ForlornFlats extends CardImpl { + + public ForlornFlats(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + this.subtype.add(SubType.DESERT); + + // Forlorn Flats enters the battlefield tapped. + this.addAbility(new EntersBattlefieldTappedAbility()); + + // When Forlorn Flats enters the battlefield, it deals 1 damage to target opponent. + Ability ability = new EntersBattlefieldTriggeredAbility(new DamageTargetEffect(1, "it")); + ability.addTarget(new TargetOpponent()); + this.addAbility(ability); + + // {T}: Add {W} or {B}. + this.addAbility(new WhiteManaAbility()); + this.addAbility(new BlackManaAbility()); + } + + private ForlornFlats(final ForlornFlats card) { + super(card); + } + + @Override + public ForlornFlats copy() { + return new ForlornFlats(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FormAPosse.java b/Mage.Sets/src/mage/cards/f/FormAPosse.java new file mode 100644 index 00000000000..f7b0afa4380 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FormAPosse.java @@ -0,0 +1,32 @@ +package mage.cards.f; + +import mage.abilities.dynamicvalue.common.ManacostVariableValue; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.game.permanent.token.MercenaryToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FormAPosse extends CardImpl { + + public FormAPosse(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{X}{R}{W}"); + + // Create X 1/1 red Mercenary creature tokens with "{T}: Target creature you control gets +1/+0 until end of turn. Activate only as a sorcery." + this.getSpellAbility().addEffect(new CreateTokenEffect(new MercenaryToken(), ManacostVariableValue.REGULAR)); + } + + private FormAPosse(final FormAPosse card) { + super(card); + } + + @Override + public FormAPosse copy() { + return new FormAPosse(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/ForsakenMiner.java b/Mage.Sets/src/mage/cards/f/ForsakenMiner.java new file mode 100644 index 00000000000..db512ff78fe --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/ForsakenMiner.java @@ -0,0 +1,49 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.common.CantBlockAbility; +import mage.abilities.common.CommittedCrimeTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.ReturnSourceFromGraveyardToBattlefieldEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Zone; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ForsakenMiner extends CardImpl { + + public ForsakenMiner(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{B}"); + + this.subtype.add(SubType.SKELETON); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Forsaken Miner can't block. + this.addAbility(new CantBlockAbility()); + + // Whenever you commit a crime, you may pay {B}. If you do, return Forsaken Miner from your graveyard to the battlefield. + this.addAbility(new CommittedCrimeTriggeredAbility( + Zone.GRAVEYARD, + new DoIfCostPaid(new ReturnSourceFromGraveyardToBattlefieldEffect(), new ManaCostsImpl<>("{B}")), + false + )); + } + + private ForsakenMiner(final ForsakenMiner card) { + super(card); + } + + @Override + public ForsakenMiner copy() { + return new ForsakenMiner(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FortuneLoyalSteed.java b/Mage.Sets/src/mage/cards/f/FortuneLoyalSteed.java new file mode 100644 index 00000000000..b9ed19dcdec --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FortuneLoyalSteed.java @@ -0,0 +1,108 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksWhileSaddledTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.delayed.AtTheEndOfCombatDelayedTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.effects.common.ExileThenReturnTargetEffect; +import mage.abilities.effects.keyword.ScryEffect; +import mage.abilities.keyword.SaddleAbility; +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.FilterCreaturePermanent; +import mage.filter.predicate.permanent.SaddledSourceThisTurnPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.targetpointer.FixedTargets; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FortuneLoyalSteed extends CardImpl { + + public FortuneLoyalSteed(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.BEAST); + this.subtype.add(SubType.MOUNT); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // When Fortune, Loyal Steed enters the battlefield, scry 2. + this.addAbility(new EntersBattlefieldTriggeredAbility(new ScryEffect(2, false))); + + // Whenever Fortune attacks while saddled, at end of combat, exile it and up to one creature that saddled it this turn, then return those cards to the battlefield under their owner's control. + this.addAbility(new AttacksWhileSaddledTriggeredAbility(new CreateDelayedTriggeredAbilityEffect( + new AtTheEndOfCombatDelayedTriggeredAbility(new FortuneLoyalSteedEffect()).setTriggerPhrase("at end of combat") + ))); + + // Saddle 1 + this.addAbility(new SaddleAbility(1)); + } + + private FortuneLoyalSteed(final FortuneLoyalSteed card) { + super(card); + } + + @Override + public FortuneLoyalSteed copy() { + return new FortuneLoyalSteed(this); + } +} + +class FortuneLoyalSteedEffect extends OneShotEffect { + + private static final FilterPermanent filter = new FilterCreaturePermanent("creature that saddled it this turn"); + + static { + filter.add(SaddledSourceThisTurnPredicate.instance); + } + + FortuneLoyalSteedEffect() { + super(Outcome.Benefit); + staticText = "exile it and up to one creature that saddled it this turn, " + + "then return those cards to the battlefield under their owner's control"; + } + + private FortuneLoyalSteedEffect(final FortuneLoyalSteedEffect effect) { + super(effect); + } + + @Override + public FortuneLoyalSteedEffect copy() { + return new FortuneLoyalSteedEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + List permanents = new ArrayList<>(); + permanents.add(source.getSourcePermanentIfItStillExists(game)); + TargetPermanent target = new TargetPermanent(0, 1, filter, true); + player.choose(outcome, target, source, game); + permanents.add(game.getPermanent(target.getFirstTarget())); + permanents.removeIf(Objects::isNull); + return !permanents.isEmpty() && new ExileThenReturnTargetEffect(false, false) + .setTargetPointer(new FixedTargets(permanents, game)) + .apply(game, source); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FranticScapegoat.java b/Mage.Sets/src/mage/cards/f/FranticScapegoat.java new file mode 100644 index 00000000000..feb8fe2daeb --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FranticScapegoat.java @@ -0,0 +1,162 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.SuspectSourceEffect; +import mage.abilities.keyword.HasteAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.PermanentReferenceInCollectionPredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeBatchEvent; +import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.Target; +import mage.target.TargetPermanent; + +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * @author notgreat + */ +public final class FranticScapegoat extends CardImpl { + + public FranticScapegoat(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{R}"); + + this.subtype.add(SubType.GOAT); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // When Frantic Scapegoat enters the battlefield, suspect it. + this.addAbility(new EntersBattlefieldTriggeredAbility(new SuspectSourceEffect())); + + // Whenever one or more other creatures enter the battlefield under your control, if Frantic Scapegoat is suspected, you may suspect one of the other creatures. If you do, Frantic Scapegoat is no longer suspected. + this.addAbility(new FranticScapegoatTriggeredAbility()); + + } + + private FranticScapegoat(final FranticScapegoat card) { + super(card); + } + + @Override + public FranticScapegoat copy() { + return new FranticScapegoat(this); + } +} + +//Based on Lightmine Field +class FranticScapegoatTriggeredAbility extends TriggeredAbilityImpl { + + FranticScapegoatTriggeredAbility() { + super(Zone.BATTLEFIELD, new FranticScapegoatSuspectEffect(), true); + } + + private FranticScapegoatTriggeredAbility(final FranticScapegoatTriggeredAbility ability) { + super(ability); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ZONE_CHANGE_BATCH; + } + + @Override + public boolean checkInterveningIfClause(Game game) { + Permanent source = getSourcePermanentIfItStillExists(game); + return (source != null && source.isSuspected()); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + ZoneChangeBatchEvent zEvent = (ZoneChangeBatchEvent) event; + Set enteringCreatures = zEvent.getEvents().stream() + .filter(z -> z.getToZone() == Zone.BATTLEFIELD) + .filter(z -> this.controllerId.equals(z.getPlayerId())) + .map(ZoneChangeEvent::getTarget) + .filter(Objects::nonNull) + .filter(permanent -> permanent.isCreature(game)) + .map(p -> new MageObjectReference(p, game)) + .collect(Collectors.toSet()); + if (!enteringCreatures.isEmpty()) { + this.getEffects().setValue("franticScapegoatEnteringCreatures", enteringCreatures); + return true; + } + return false; + } + + @Override + public FranticScapegoatTriggeredAbility copy() { + return new FranticScapegoatTriggeredAbility(this); + } + + @Override + public String getRule() { + return "Whenever one or more other creatures enter the battlefield under your control, if {this} is suspected, you may suspect one of the other creatures. If you do, {this} is no longer suspected."; + } +} + +class FranticScapegoatSuspectEffect extends OneShotEffect { + + FranticScapegoatSuspectEffect() { + super(Outcome.Benefit); + this.staticText = "Suspect one of the other creatures"; + } + + private FranticScapegoatSuspectEffect(final FranticScapegoatSuspectEffect effect) { + super(effect); + } + + @Override + public FranticScapegoatSuspectEffect copy() { + return new FranticScapegoatSuspectEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Set enteringSet = (Set) getValue("franticScapegoatEnteringCreatures"); + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null && enteringSet != null) { + Permanent suspect = null; + if (enteringSet.size() > 1) { + FilterCreaturePermanent filter = new FilterCreaturePermanent("one of those creatures"); + filter.add(new PermanentReferenceInCollectionPredicate(enteringSet)); + Target target = new TargetPermanent(filter); + target.withNotTarget(true); + if (controller.choose(outcome, target, source, game)) { + suspect = game.getPermanent(target.getFirstTarget()); + } + } else { //There is only 1 creature in the set + for (MageObjectReference s : enteringSet) { + suspect = s.getPermanent(game); + } + } + if (suspect != null) { + suspect.setSuspected(true, game, source); + Permanent scapegoat = source.getSourcePermanentIfItStillExists(game); + if (scapegoat != null) { + scapegoat.setSuspected(false, game, source); + } + } + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/f/FreestriderCommando.java b/Mage.Sets/src/mage/cards/f/FreestriderCommando.java new file mode 100644 index 00000000000..10141dd058c --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FreestriderCommando.java @@ -0,0 +1,78 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.condition.Condition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.PlotAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.stack.Spell; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class FreestriderCommando extends CardImpl { + + public FreestriderCommando(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.subtype.add(SubType.CENTAUR); + this.subtype.add(SubType.MERCENARY); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Freestrider Commando enters the battlefield with two +1/+1 counters on it if it wasn't cast or no mana was spent to cast it. + this.addAbility(new EntersBattlefieldAbility( + new ConditionalOneShotEffect( + new AddCountersSourceEffect(CounterType.P1P1.createInstance(2)), + FreestriderCommandoCondition.instance, "" + ), "with two +1/+1 counters on it if it wasn't cast or no mana was spent to cast it" + )); + + // Plot {3}{G} + this.addAbility(new PlotAbility("{3}{G}")); + } + + private FreestriderCommando(final FreestriderCommando card) { + super(card); + } + + @Override + public FreestriderCommando copy() { + return new FreestriderCommando(this); + } +} + +enum FreestriderCommandoCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanentEntering(source.getSourceId()); + if (permanent == null) { + return false; + } + // Check if the spell exists on the stack + Spell spell = game.getStack().getSpell(source.getSourceId()); + if (spell == null) { + return true; // cannot find the spell, so it wasn't cast. + } + // spell was found, did it cost mana? + return 0 == spell.getStackAbility().getManaCostsToPay().getUsedManaToPay().count(); + } + + @Override + public String toString() { + return "if it wasn't cast or no mana was spent to cast it"; + } +} diff --git a/Mage.Sets/src/mage/cards/f/FreestriderLookout.java b/Mage.Sets/src/mage/cards/f/FreestriderLookout.java new file mode 100644 index 00000000000..8098b2b59f9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FreestriderLookout.java @@ -0,0 +1,47 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.common.CommittedCrimeTriggeredAbility; +import mage.abilities.effects.common.LookLibraryAndPickControllerEffect; +import mage.abilities.keyword.ReachAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.PutCards; +import mage.constants.SubType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FreestriderLookout extends CardImpl { + + public FreestriderLookout(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // Whenever you commit a crime, look at the top five cards of your library. You may put a land card from among them onto the battlefield tapped. Put the rest on the bottom of your library in a random order. This ability triggers only once each turn. + this.addAbility(new CommittedCrimeTriggeredAbility(new LookLibraryAndPickControllerEffect( + 5, 1, StaticFilters.FILTER_CARD_LAND_A, + PutCards.BATTLEFIELD_TAPPED, PutCards.BOTTOM_RANDOM + )).setTriggersOnceEachTurn(true)); + } + + private FreestriderLookout(final FreestriderLookout card) { + super(card); + } + + @Override + public FreestriderLookout copy() { + return new FreestriderLookout(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FrontierSeeker.java b/Mage.Sets/src/mage/cards/f/FrontierSeeker.java new file mode 100644 index 00000000000..ede035d765c --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FrontierSeeker.java @@ -0,0 +1,54 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.LookLibraryAndPickControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.PutCards; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicates; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FrontierSeeker extends CardImpl { + + private static final FilterCard filter = new FilterCard("a Mount creature card or a Plains card"); + + static { + filter.add(Predicates.or( + Predicates.and( + SubType.MOUNT.getPredicate(), + CardType.CREATURE.getPredicate() + ), SubType.PLAINS.getPredicate() + )); + } + + public FrontierSeeker(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SCOUT); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // When Frontier Seeker enters the battlefield, look at the top five cards of your library. You may reveal a Mount creature card or a Plains card from among them and put it into your hand. Put the rest on the bottom of your library in a random order. + this.addAbility(new EntersBattlefieldTriggeredAbility(new LookLibraryAndPickControllerEffect( + 5, 1, filter, PutCards.HAND, PutCards.BOTTOM_RANDOM, true + ))); + } + + private FrontierSeeker(final FrontierSeeker card) { + super(card); + } + + @Override + public FrontierSeeker copy() { + return new FrontierSeeker(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FugitiveCodebreaker.java b/Mage.Sets/src/mage/cards/f/FugitiveCodebreaker.java new file mode 100644 index 00000000000..e21d8c6fc0d --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FugitiveCodebreaker.java @@ -0,0 +1,98 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.TurnedFaceUpSourceTriggeredAbility; +import mage.abilities.costs.CostAdjuster; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.discard.DiscardHandControllerEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.abilities.keyword.DisguiseAbility; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.ProwessAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class FugitiveCodebreaker extends CardImpl { + + public FugitiveCodebreaker(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}"); + + this.subtype.add(SubType.GOBLIN); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // Prowess + this.addAbility(new ProwessAbility()); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // Disguise {5}{R}. This cost is reduced by {1} for each instant and sorcery card in your graveyard. + this.addAbility(new FugitiveCodebreakerDisguiseAbility(this)); + + // When Fugitive Codebreaker is turned face up, discard your hand, then draw three cards. + Ability ability = new TurnedFaceUpSourceTriggeredAbility(new DiscardHandControllerEffect()); + ability.addEffect(new DrawCardSourceControllerEffect(3).concatBy(", then")); + this.addAbility(ability); + } + + private FugitiveCodebreaker(final FugitiveCodebreaker card) { + super(card); + } + + @Override + public FugitiveCodebreaker copy() { + return new FugitiveCodebreaker(this); + } +} + +class FugitiveCodebreakerDisguiseAbility extends DisguiseAbility { + + static final DynamicValue xValue = new CardsInControllerGraveyardCount(StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY_FROM_YOUR_GRAVEYARD); + private static final Hint hint = new ValueHint("instant and/or sorcery in your graveyard", xValue); + + FugitiveCodebreakerDisguiseAbility(Card card) { + super(card, new ManaCostsImpl<>("{5}{R}"), FugitiveCodebreakerAdjuster.instance); + addHint(hint); + } + + private FugitiveCodebreakerDisguiseAbility(final FugitiveCodebreakerDisguiseAbility ability) { + super(ability); + } + + @Override + public FugitiveCodebreakerDisguiseAbility copy() { + return new FugitiveCodebreakerDisguiseAbility(this); + } + + @Override + public String getRule() { + return "Disguise {5}{R}. This cost is reduced by {1} for each instant and sorcery card in your graveyard."; + } +} + +enum FugitiveCodebreakerAdjuster implements CostAdjuster { + instance; + + @Override + public void adjustCosts(Ability ability, Game game) { + CardUtil.reduceCost(ability, FugitiveCodebreakerDisguiseAbility.xValue.calculate(game, ability, null)); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FullSteamAhead.java b/Mage.Sets/src/mage/cards/f/FullSteamAhead.java new file mode 100644 index 00000000000..f49e3084c59 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FullSteamAhead.java @@ -0,0 +1,47 @@ +package mage.cards.f; + +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.combat.CantBeBlockedByMoreThanOneSourceEffect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FullSteamAhead extends CardImpl { + + public FullSteamAhead(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{G}{G}"); + + // Until end of turn, each creature you control gets +2/+2 and gains trample and "This creature can't be blocked by more than one creature." + this.getSpellAbility().addEffect(new BoostControlledEffect( + 2, 2, Duration.EndOfTurn + ).setText("until end of turn, each creature you control gets +2/+2")); + this.getSpellAbility().addEffect(new GainAbilityControlledEffect( + TrampleAbility.getInstance(), Duration.EndOfTurn, + StaticFilters.FILTER_PERMANENT_CREATURES + ).setText("and gains trample")); + this.getSpellAbility().addEffect(new GainAbilityControlledEffect( + new SimpleStaticAbility(new CantBeBlockedByMoreThanOneSourceEffect() + .setText("this creature can't be blocked by more than one creature")), + Duration.EndOfTurn, StaticFilters.FILTER_PERMANENT_CREATURES + ).setText("and \"This creature can't be blocked by more than one creature.\"")); + } + + private FullSteamAhead(final FullSteamAhead card) { + super(card); + } + + @Override + public FullSteamAhead copy() { + return new FullSteamAhead(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FutureSight.java b/Mage.Sets/src/mage/cards/f/FutureSight.java index b8427292165..fa7cda7e9c3 100644 --- a/Mage.Sets/src/mage/cards/f/FutureSight.java +++ b/Mage.Sets/src/mage/cards/f/FutureSight.java @@ -1,7 +1,7 @@ package mage.cards.f; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.common.continuous.PlayTheTopCardEffect; +import mage.abilities.effects.common.continuous.PlayFromTopOfLibraryEffect; import mage.abilities.effects.common.continuous.PlayWithTheTopCardRevealedEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -22,7 +22,7 @@ public final class FutureSight extends CardImpl { this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PlayWithTheTopCardRevealedEffect())); // You may play lands and cast spells from the top of your library. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PlayTheTopCardEffect())); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PlayFromTopOfLibraryEffect())); } private FutureSight(final FutureSight card) { diff --git a/Mage.Sets/src/mage/cards/g/GaleWaterdeepProdigy.java b/Mage.Sets/src/mage/cards/g/GaleWaterdeepProdigy.java index d384dad0993..c7a50440a70 100644 --- a/Mage.Sets/src/mage/cards/g/GaleWaterdeepProdigy.java +++ b/Mage.Sets/src/mage/cards/g/GaleWaterdeepProdigy.java @@ -1,26 +1,20 @@ package mage.cards.g; -import mage.ApprovingObject; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.ChooseABackgroundAbility; import mage.abilities.common.SpellCastControllerTriggeredAbility; -import mage.abilities.effects.ContinuousEffect; -import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.ReplacementEffectImpl; -import mage.cards.Card; +import mage.abilities.effects.common.MayCastTargetCardEffect; 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.filter.FilterCard; import mage.filter.common.FilterInstantOrSorcerySpell; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.events.ZoneChangeEvent; import mage.game.stack.Spell; -import mage.players.Player; import mage.target.common.TargetCardInYourGraveyard; -import mage.target.targetpointer.FixedTarget; import mage.watchers.common.CastFromHandWatcher; import java.util.UUID; @@ -69,9 +63,13 @@ class GaleWaterdeepProdigyTriggeredAbility extends SpellCastControllerTriggeredA } public GaleWaterdeepProdigyTriggeredAbility() { - super(new GaleWaterdeepProdigyEffect(), + super( + new MayCastTargetCardEffect(true) + .setText("you may cast up to one target card of the other type from your graveyard. " + + "If a spell cast from your graveyard this way would be put into your graveyard, exile it instead."), new FilterInstantOrSorcerySpell("an instant or sorcery spell from your hand"), - false); + false + ); addWatcher(new CastFromHandWatcher()); } @@ -110,81 +108,4 @@ class GaleWaterdeepProdigyTriggeredAbility extends SpellCastControllerTriggeredA public GaleWaterdeepProdigyTriggeredAbility copy() { return new GaleWaterdeepProdigyTriggeredAbility(this); } -} - -class GaleWaterdeepProdigyEffect extends OneShotEffect { - - GaleWaterdeepProdigyEffect() { - super(Outcome.PutCardInPlay); - this.staticText = "you may cast up to one of the other type from your graveyard. " + - "If a spell cast from your graveyard this way would be put into your graveyard, exile it instead."; - } - - private GaleWaterdeepProdigyEffect(final GaleWaterdeepProdigyEffect effect) { - super(effect); - } - - @Override - public GaleWaterdeepProdigyEffect copy() { - return new GaleWaterdeepProdigyEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller == null) { - return false; - } - Card card = game.getCard(this.getTargetPointer().getFirst(game, source)); - if (card != null - && controller.chooseUse(Outcome.Neutral, "Cast " + card.getLogName() + '?', source, game)) { - game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE); - controller.cast(controller.chooseAbilityForCast(card, game, false), - game, false, new ApprovingObject(source, game)); - game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null); - ContinuousEffect effect = new GaleWaterdeepProdigyReplacementEffect(card.getId()); - effect.setTargetPointer(new FixedTarget(card.getId(), game.getState().getZoneChangeCounter(card.getId()))); - game.addEffect(effect, source); - } - return true; - } -} - -class GaleWaterdeepProdigyReplacementEffect extends ReplacementEffectImpl { - - private final UUID cardId; - - GaleWaterdeepProdigyReplacementEffect(UUID cardId) { - super(Duration.EndOfTurn, Outcome.Exile); - this.cardId = cardId; - staticText = "If a spell cast from your graveyard this way would be put into your graveyard, exile it instead."; - } - - private GaleWaterdeepProdigyReplacementEffect(final GaleWaterdeepProdigyReplacementEffect effect) { - super(effect); - this.cardId = effect.cardId; - } - - @Override - public GaleWaterdeepProdigyReplacementEffect copy() { - return new GaleWaterdeepProdigyReplacementEffect(this); - } - - @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - ((ZoneChangeEvent) event).setToZone(Zone.EXILED); - return false; - } - - @Override - public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.ZONE_CHANGE; - } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - ZoneChangeEvent zEvent = (ZoneChangeEvent) event; - return zEvent.getToZone() == Zone.GRAVEYARD - && zEvent.getTargetId().equals(this.cardId); - } -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/g/GaleaKindlerOfHope.java b/Mage.Sets/src/mage/cards/g/GaleaKindlerOfHope.java index 33d9f9cd119..55a9e824ab2 100644 --- a/Mage.Sets/src/mage/cards/g/GaleaKindlerOfHope.java +++ b/Mage.Sets/src/mage/cards/g/GaleaKindlerOfHope.java @@ -9,7 +9,7 @@ import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.common.InfoEffect; import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; -import mage.abilities.effects.common.continuous.PlayTheTopCardEffect; +import mage.abilities.effects.common.continuous.PlayFromTopOfLibraryEffect; import mage.abilities.keyword.VigilanceAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -53,7 +53,7 @@ public final class GaleaKindlerOfHope extends CardImpl { this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); // You may cast Aura and Equipment spells from the top of your library. When you cast an Equipment spell this way, it gains "When this Equipment enters the battlefield, attach it to target creature you control." - Ability ability = new SimpleStaticAbility(new PlayTheTopCardEffect(TargetController.YOU, filter, false)); + Ability ability = new SimpleStaticAbility(new PlayFromTopOfLibraryEffect(filter)); ability.addEffect(new InfoEffect("When you cast an Equipment spell this way, it gains " + "\"When this Equipment enters the battlefield, attach it to target creature you control.\"")); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/g/Galvanoth.java b/Mage.Sets/src/mage/cards/g/Galvanoth.java index 4524cf1fa8e..521576041cc 100644 --- a/Mage.Sets/src/mage/cards/g/Galvanoth.java +++ b/Mage.Sets/src/mage/cards/g/Galvanoth.java @@ -1,18 +1,20 @@ package mage.cards.g; -import java.util.UUID; -import mage.ApprovingObject; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; import mage.abilities.effects.OneShotEffect; -import mage.cards.*; -import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.SubType; -import mage.constants.TargetController; +import mage.abilities.effects.common.MayCastTargetCardEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.CardsImpl; +import mage.constants.*; import mage.game.Game; import mage.players.Player; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; /** * @author North @@ -45,8 +47,9 @@ class GalvanothEffect extends OneShotEffect { GalvanothEffect() { super(Outcome.PlayForFree); - staticText = "look at the top card of your library. If it's an instant or " - + "sorcery card, you may cast it without paying its mana cost"; + staticText = "look at the top card of your library. " + + "You may cast it without paying its mana cost " + + "if it's an instant or sorcery spell"; } private GalvanothEffect(final GalvanothEffect effect) { @@ -61,12 +64,9 @@ class GalvanothEffect extends OneShotEffect { if (card != null) { controller.lookAtCards(source, null, new CardsImpl(card), game); if (card.isInstantOrSorcery(game)) { - if (controller.chooseUse(Outcome.PlayForFree, "Cast " + card.getName() + " without paying its mana cost?", source, game)) { - game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE); - controller.cast(controller.chooseAbilityForCast(card, game, true), - game, true, new ApprovingObject(source, game)); - game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null); - } + new MayCastTargetCardEffect(CastManaAdjustment.WITHOUT_PAYING_MANA_COST) + .setTargetPointer(new FixedTarget(card.getId())) + .apply(game, source); } } return true; diff --git a/Mage.Sets/src/mage/cards/g/GarruksHorde.java b/Mage.Sets/src/mage/cards/g/GarruksHorde.java index 7b00eca127f..0f5d11934e0 100644 --- a/Mage.Sets/src/mage/cards/g/GarruksHorde.java +++ b/Mage.Sets/src/mage/cards/g/GarruksHorde.java @@ -2,7 +2,7 @@ package mage.cards.g; import mage.MageInt; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.common.continuous.PlayTheTopCardEffect; +import mage.abilities.effects.common.continuous.PlayFromTopOfLibraryEffect; import mage.abilities.effects.common.continuous.PlayWithTheTopCardRevealedEffect; import mage.abilities.keyword.TrampleAbility; import mage.cards.CardImpl; @@ -36,7 +36,7 @@ public final class GarruksHorde extends CardImpl { this.addAbility(new SimpleStaticAbility(new PlayWithTheTopCardRevealedEffect())); // You may cast creature spells from the top of your library. - this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(TargetController.YOU, filter, false))); + this.addAbility(new SimpleStaticAbility(new PlayFromTopOfLibraryEffect(filter))); } private GarruksHorde(final GarruksHorde card) { diff --git a/Mage.Sets/src/mage/cards/g/GaryClone.java b/Mage.Sets/src/mage/cards/g/GaryClone.java index a9db219d974..f7045456015 100644 --- a/Mage.Sets/src/mage/cards/g/GaryClone.java +++ b/Mage.Sets/src/mage/cards/g/GaryClone.java @@ -2,6 +2,7 @@ package mage.cards.g; import mage.MageInt; import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.common.continuous.BoostAllEffect; import mage.abilities.keyword.SquadAbility; import mage.cards.CardImpl; @@ -37,7 +38,7 @@ public final class GaryClone extends CardImpl { this.toughness = new MageInt(3); // Squad {2} - this.addAbility(new SquadAbility()); + this.addAbility(new SquadAbility(new GenericManaCost(2))); // Whenever Gary Clone attacks, each creature you control named Gary Clone gets +1/+0 until end of turn. this.addAbility(new AttacksTriggeredAbility(new BoostAllEffect( diff --git a/Mage.Sets/src/mage/cards/g/GenerousPlunderer.java b/Mage.Sets/src/mage/cards/g/GenerousPlunderer.java new file mode 100644 index 00000000000..a6fd6fcf314 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GenerousPlunderer.java @@ -0,0 +1,104 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.CreateTokenTargetEffect; +import mage.abilities.effects.common.DamageTargetEffect; +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.filter.predicate.permanent.DefendingPlayerControlsSourceAttackingPredicate; +import mage.game.Game; +import mage.game.permanent.token.TreasureToken; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class GenerousPlunderer extends CardImpl { + + private static final FilterPermanent filter + = new FilterArtifactPermanent("artifacts permanents controlled by defending player"); + + static { + filter.add(DefendingPlayerControlsSourceAttackingPredicate.instance); + } + + private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter); + + public GenerousPlunderer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Menace + this.addAbility(new MenaceAbility()); + + // At the beginning of your upkeep, you may create a Treasure token. When you do, target opponent creates a tapped Treasure token. + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new GenerousPlundererEffect(), TargetController.YOU, true)); + + // Whenever Generous Plunderer attacks, it deals damage to defending player equal to the number of artifacts they control. + this.addAbility(new AttacksTriggeredAbility( + new DamageTargetEffect(xValue), false, + "Whenever {this} attacks, it deals damage to defending player equal to the number of artifacts they control.", + SetTargetPointer.PLAYER + )); + } + + private GenerousPlunderer(final GenerousPlunderer card) { + super(card); + } + + @Override + public GenerousPlunderer copy() { + return new GenerousPlunderer(this); + } +} + +class GenerousPlundererEffect extends OneShotEffect { + + GenerousPlundererEffect() { + super(Outcome.Benefit); + staticText = "you may create a Treasure token. When you do, target opponent creates a tapped Treasure token."; + } + + private GenerousPlundererEffect(final GenerousPlundererEffect effect) { + super(effect); + } + + @Override + public GenerousPlundererEffect copy() { + return new GenerousPlundererEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + boolean flag = new CreateTokenEffect(new TreasureToken()) + .apply(game, source); + if (!flag) { + return false; + } + ReflexiveTriggeredAbility reflexive = new ReflexiveTriggeredAbility( + new CreateTokenTargetEffect(new TreasureToken(), 1, true), false + ); + reflexive.addTarget(new TargetOpponent()); + game.fireReflexiveTriggeredAbility(reflexive, source); + return true; + } + +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/g/GeralfTheFleshwright.java b/Mage.Sets/src/mage/cards/g/GeralfTheFleshwright.java new file mode 100644 index 00000000000..c4c179a2eac --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GeralfTheFleshwright.java @@ -0,0 +1,211 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; +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.counter.AddCountersTargetEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.ZombieRogueToken; +import mage.players.Player; +import mage.watchers.Watcher; +import mage.watchers.common.SpellsCastWatcher; + +import java.util.*; + +/** + * @author Susucr + */ +public final class GeralfTheFleshwright extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent(SubType.ZOMBIE, "Zombie"); + private static final Hint hint = new ValueHint("Number of Zombies that entered this turn", GeralfTheFleshwrightValue.instance); + + public GeralfTheFleshwright(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(2); + this.toughness = new MageInt(3); + + // Whenever you cast a spell during your turn other than your first spell that turn, create a 2/2 blue and black Zombie Rogue creature token. + this.addAbility(new GeralfTheFleshwrightTriggeredAbility()); + + // Whenever a Zombie enters the battlefield under your control, put a +1/+1 counter on it for each other Zombie that entered the battlefield under your control this turn. + this.addAbility(new EntersBattlefieldControlledTriggeredAbility( + Zone.BATTLEFIELD, + new GeralfTheFleshwrightEffect(), + filter, + false, + SetTargetPointer.PERMANENT + ).addHint(hint), new GeralfTheFleshwrightWatcher()); + } + + private GeralfTheFleshwright(final GeralfTheFleshwright card) { + super(card); + } + + @Override + public GeralfTheFleshwright copy() { + return new GeralfTheFleshwright(this); + } +} + + +class GeralfTheFleshwrightTriggeredAbility extends TriggeredAbilityImpl { + + public GeralfTheFleshwrightTriggeredAbility() { + super(Zone.BATTLEFIELD, new CreateTokenEffect(new ZombieRogueToken())); + setTriggerPhrase("Whenever you cast a spell during your turn other than your first spell that turn, "); + } + + private GeralfTheFleshwrightTriggeredAbility(final GeralfTheFleshwrightTriggeredAbility ability) { + super(ability); + } + + @Override + public GeralfTheFleshwrightTriggeredAbility copy() { + return new GeralfTheFleshwrightTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.SPELL_CAST; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (event.getPlayerId().equals(getControllerId()) && game.isActivePlayer(getControllerId())) { + SpellsCastWatcher watcher = game.getState().getWatcher(SpellsCastWatcher.class); + return watcher != null && watcher.getSpellsCastThisTurn(event.getPlayerId()).size() > 1; + } + return false; + } +} + + +class GeralfTheFleshwrightWatcher extends Watcher { + + // player -> MOR of zombies that entered this turn under that player control. + private final Map> enteredThisTurn = new HashMap<>(); + + GeralfTheFleshwrightWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() != GameEvent.EventType.ENTERS_THE_BATTLEFIELD) { + return; + } + + Permanent permanent = game.getPermanentOrLKIBattlefield(event.getTargetId()); + if (permanent == null || !permanent.getSubtype().contains(SubType.ZOMBIE)) { + return; + } + + Player controller = game.getPlayer(event.getPlayerId()); + if (controller == null) { + return; + } + enteredThisTurn.computeIfAbsent(controller.getId(), (k -> new HashSet<>())) + .add(new MageObjectReference(permanent, game)); + } + + @Override + public void reset() { + super.reset(); + enteredThisTurn.clear(); + } + + int getZombiesThatEnteredThisTurn(UUID playerId, MageObjectReference toExclude) { + if (toExclude == null) { + return enteredThisTurn + .getOrDefault(playerId, Collections.emptySet()) + .size(); + } else { + return enteredThisTurn + .getOrDefault(playerId, Collections.emptySet()) + .stream() + .filter(mor -> !toExclude.equals(mor)) + .mapToInt(x -> 1) + .sum(); + } + } +} + +enum GeralfTheFleshwrightValue implements DynamicValue { + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + GeralfTheFleshwrightWatcher watcher = game.getState().getWatcher(GeralfTheFleshwrightWatcher.class); + Player controller = game.getPlayer(sourceAbility.getControllerId()); + if (watcher == null || controller == null) { + return 0; + } + return watcher.getZombiesThatEnteredThisTurn(controller.getId(), null); + } + + @Override + public GeralfTheFleshwrightValue copy() { + return instance; + } + + @Override + public String getMessage() { + return "Number of Zombies that entered this turn"; + } +} + +class GeralfTheFleshwrightEffect extends OneShotEffect { + + GeralfTheFleshwrightEffect() { + super(Outcome.BoostCreature); + staticText = "put a +1/+1 counter on it for each other Zombie that entered the battlefield under your control this turn"; + } + + private GeralfTheFleshwrightEffect(final GeralfTheFleshwrightEffect effect) { + super(effect); + } + + @Override + public GeralfTheFleshwrightEffect copy() { + return new GeralfTheFleshwrightEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + GeralfTheFleshwrightWatcher watcher = game.getState().getWatcher(GeralfTheFleshwrightWatcher.class); + if (watcher == null) { + return false; + } + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent == null) { + return false; + } + int count = watcher.getZombiesThatEnteredThisTurn(source.getControllerId(), new MageObjectReference(permanent, game)); + if (count > 0) { + new AddCountersTargetEffect(CounterType.P1P1.createInstance(count)) + .setTargetPointer(getTargetPointer().copy()) + .apply(game, source); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/g/GetawayGlamer.java b/Mage.Sets/src/mage/cards/g/GetawayGlamer.java new file mode 100644 index 00000000000..830ccc6e3ba --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GetawayGlamer.java @@ -0,0 +1,99 @@ +package mage.cards.g; + +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileReturnBattlefieldNextEndStepTargetEffect; +import mage.abilities.keyword.SpreeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetCreaturePermanent; + +import java.util.List; +import java.util.UUID; + +/** + * @author Susucr + */ +public final class GetawayGlamer extends CardImpl { + + private static final FilterCreaturePermanent filter = + new FilterCreaturePermanent("target nontoken creature"); + + static { + filter.add(TokenPredicate.FALSE); + } + + public GetawayGlamer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{W}"); + + // Spree + this.addAbility(new SpreeAbility(this)); + + // + {1} -- Exile target nontoken creature. Return it to the battlefield under its owner's control at the beginning of the next end step. + this.getSpellAbility().addEffect(new ExileReturnBattlefieldNextEndStepTargetEffect().withTextThatCard(false)); + this.getSpellAbility().addTarget(new TargetCreaturePermanent(filter)); + this.getSpellAbility().withFirstModeCost(new GenericManaCost(1)); + + // + {2} -- Destroy target creature if no other creature has greater power. + this.getSpellAbility().addMode(new Mode(new GetawayGlamerEffect()) + .addTarget(new TargetCreaturePermanent()) + .withCost(new GenericManaCost(2))); + } + + private GetawayGlamer(final GetawayGlamer card) { + super(card); + } + + @Override + public GetawayGlamer copy() { + return new GetawayGlamer(this); + } +} + +class GetawayGlamerEffect extends OneShotEffect { + + GetawayGlamerEffect() { + super(Outcome.DestroyPermanent); + this.staticText = "Destroy target creature if no other creature has greater power"; + } + + private GetawayGlamerEffect(final GetawayGlamerEffect effect) { + super(effect); + } + + @Override + public GetawayGlamerEffect copy() { + return new GetawayGlamerEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent targetCreature = game.getPermanent(source.getFirstTarget()); + if (targetCreature == null) { + return false; + } + + int powerOfTarget = targetCreature.getPower().getValue(); + List creatures = game.getBattlefield() + .getActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, source.getControllerId(), source, game); + for (Permanent creature : creatures) { + // No check for other, since the check is "greater than" + if (creature.getPower().getValue() > powerOfTarget) { + // Found another creature with greater power. no effect. + return false; + } + } + + targetCreature.destroy(source, game, false); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/g/GeyserDrake.java b/Mage.Sets/src/mage/cards/g/GeyserDrake.java new file mode 100644 index 00000000000..dcc23648826 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GeyserDrake.java @@ -0,0 +1,48 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.NotMyTurnCondition; +import mage.abilities.decorator.ConditionalCostModificationEffect; +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.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GeyserDrake extends CardImpl { + + public GeyserDrake(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(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // As long as it's not your turn, spells you cast cost {1} less to cast. + this.addAbility(new SimpleStaticAbility(new ConditionalCostModificationEffect( + new SpellsCostReductionControllerEffect(StaticFilters.FILTER_CARD, 1), + NotMyTurnCondition.instance, "as long as it's not your turn, " + + "spells you cast cost {1} less to cast" + ))); + } + + private GeyserDrake(final GeyserDrake card) { + super(card); + } + + @Override + public GeyserDrake copy() { + return new GeyserDrake(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GhiredMirrorOfTheWilds.java b/Mage.Sets/src/mage/cards/g/GhiredMirrorOfTheWilds.java new file mode 100644 index 00000000000..649dc5dc663 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GhiredMirrorOfTheWilds.java @@ -0,0 +1,67 @@ +package mage.cards.g; + +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.effects.common.CreateTokenCopyTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.HasteAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.permanent.EnteredThisTurnPredicate; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GhiredMirrorOfTheWilds extends CardImpl { + + private static final FilterPermanent filter + = new FilterControlledPermanent("token you control that entered the battlefield this turn"); + + static { + filter.add(TokenPredicate.TRUE); + filter.add(EnteredThisTurnPredicate.instance); + } + + public GhiredMirrorOfTheWilds(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{R}{G}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SHAMAN); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // Nontoken creatures you control have "{T}: Create a token that's a copy of target token you control that entered the battlefield this turn." + Ability ability = new SimpleActivatedAbility(new CreateTokenCopyTargetEffect(), new TapSourceCost()); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect( + ability, Duration.WhileOnBattlefield, StaticFilters.FILTER_CREATURES_NON_TOKEN + ))); + } + + private GhiredMirrorOfTheWilds(final GhiredMirrorOfTheWilds card) { + super(card); + } + + @Override + public GhiredMirrorOfTheWilds copy() { + return new GhiredMirrorOfTheWilds(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GiantBeaver.java b/Mage.Sets/src/mage/cards/g/GiantBeaver.java new file mode 100644 index 00000000000..a167d691449 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GiantBeaver.java @@ -0,0 +1,60 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksWhileSaddledTriggeredAbility; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.keyword.SaddleAbility; +import mage.abilities.keyword.VigilanceAbility; +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.FilterCreaturePermanent; +import mage.filter.predicate.permanent.SaddledSourceThisTurnPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GiantBeaver extends CardImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent("creature that saddled it this turn"); + + static { + filter.add(SaddledSourceThisTurnPredicate.instance); + } + + public GiantBeaver(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}"); + + this.subtype.add(SubType.BEAVER); + this.subtype.add(SubType.MOUNT); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Whenever Giant Beaver attacks while saddled, put a +1/+1 counter on target creature that saddled it this turn. + Ability ability = new AttacksWhileSaddledTriggeredAbility(new AddCountersTargetEffect(CounterType.P1P1.createInstance())); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + + // Saddle 3 + this.addAbility(new SaddleAbility(3)); + } + + private GiantBeaver(final GiantBeaver card) { + super(card); + } + + @Override + public GiantBeaver copy() { + return new GiantBeaver(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GilaCourser.java b/Mage.Sets/src/mage/cards/g/GilaCourser.java new file mode 100644 index 00000000000..2a6cc613269 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GilaCourser.java @@ -0,0 +1,45 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.common.AttacksWhileSaddledTriggeredAbility; +import mage.abilities.effects.common.ExileTopXMayPlayUntilEffect; +import mage.abilities.keyword.SaddleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GilaCourser extends CardImpl { + + public GilaCourser(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); + + this.subtype.add(SubType.LIZARD); + this.subtype.add(SubType.MOUNT); + this.power = new MageInt(4); + this.toughness = new MageInt(2); + + // Whenever Gila Courser attacks while saddled, exile the top card of your library. Until the end of your next turn, you may play that card. + this.addAbility(new AttacksWhileSaddledTriggeredAbility( + new ExileTopXMayPlayUntilEffect(1, Duration.UntilEndOfYourNextTurn) + )); + + // Saddle 1 + this.addAbility(new SaddleAbility(1)); + } + + private GilaCourser(final GilaCourser card) { + super(card); + } + + @Override + public GilaCourser copy() { + return new GilaCourser(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GisaAndGeralf.java b/Mage.Sets/src/mage/cards/g/GisaAndGeralf.java index 8cab6cf72f9..b3520a300a4 100644 --- a/Mage.Sets/src/mage/cards/g/GisaAndGeralf.java +++ b/Mage.Sets/src/mage/cards/g/GisaAndGeralf.java @@ -1,25 +1,17 @@ package mage.cards.g; -import java.util.HashSet; -import java.util.Set; import mage.MageInt; -import mage.abilities.Ability; +import mage.abilities.common.CastFromGraveyardOnceEachTurnAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.AsThoughEffectImpl; import mage.abilities.effects.common.MillCardsControllerEffect; -import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.watchers.Watcher; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.common.FilterCreatureCard; import java.util.UUID; -import mage.MageIdentifier; -import mage.MageObjectReference; -import mage.game.permanent.Permanent; /** * @@ -27,6 +19,11 @@ import mage.game.permanent.Permanent; */ public final class GisaAndGeralf extends CardImpl { + private static final FilterCreatureCard filter = new FilterCreatureCard("a Zombie creature spell"); + static { + filter.add(SubType.ZOMBIE.getPredicate()); + } + public GisaAndGeralf(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}{B}"); this.supertype.add(SuperType.LEGENDARY); @@ -38,10 +35,8 @@ public final class GisaAndGeralf extends CardImpl { // When Gisa and Geralf enters the battlefield, put the top four cards of your library into your graveyard. this.addAbility(new EntersBattlefieldTriggeredAbility(new MillCardsControllerEffect(4))); - // During each of your turns, you may cast a Zombie creature card from your graveyard. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new GisaAndGeralfCastFromGraveyardEffect()) - .setIdentifier(MageIdentifier.GisaAndGeralfWatcher), - new GisaAndGeralfWatcher()); + // Once during each of your turns, you may cast a Zombie creature spell from your graveyard + this.addAbility(new CastFromGraveyardOnceEachTurnAbility(filter)); } private GisaAndGeralf(final GisaAndGeralf card) { @@ -53,71 +48,3 @@ public final class GisaAndGeralf extends CardImpl { return new GisaAndGeralf(this); } } - -class GisaAndGeralfCastFromGraveyardEffect extends AsThoughEffectImpl { - - GisaAndGeralfCastFromGraveyardEffect() { - super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.PutCreatureInPlay); - staticText = "Once during each of your turns, you may cast a Zombie creature spell from your graveyard"; - } - - private GisaAndGeralfCastFromGraveyardEffect(final GisaAndGeralfCastFromGraveyardEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - return true; - } - - @Override - public GisaAndGeralfCastFromGraveyardEffect copy() { - return new GisaAndGeralfCastFromGraveyardEffect(this); - } - - @Override - public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { - if (source.isControlledBy(affectedControllerId) - && Zone.GRAVEYARD.equals(game.getState().getZone(objectId))) { - Card objectCard = game.getCard(objectId); - Permanent sourceObject = game.getPermanent(source.getSourceId()); - if (sourceObject != null && objectCard != null - && objectCard.isOwnedBy(source.getControllerId()) - && objectCard.isCreature(game) - && objectCard.hasSubtype(SubType.ZOMBIE, game) - && objectCard.getSpellAbility() != null - && objectCard.getSpellAbility().spellCanBeActivatedRegularlyNow(affectedControllerId, game)) { - GisaAndGeralfWatcher watcher = game.getState().getWatcher(GisaAndGeralfWatcher.class); - return watcher != null && !watcher.isAbilityUsed(new MageObjectReference(sourceObject, game)); - } - } - return false; - } -} - -class GisaAndGeralfWatcher extends Watcher { - - private final Set usedFrom = new HashSet<>(); - - GisaAndGeralfWatcher() { - super(WatcherScope.GAME); - } - - @Override - public void watch(GameEvent event, Game game) { - if (GameEvent.EventType.SPELL_CAST.equals(event.getType()) - && event.hasApprovingIdentifier(MageIdentifier.GisaAndGeralfWatcher)) { - usedFrom.add(event.getAdditionalReference().getApprovingMageObjectReference()); - } - } - - @Override - public void reset() { - super.reset(); - usedFrom.clear(); - } - - public boolean isAbilityUsed(MageObjectReference mor) { - return usedFrom.contains(mor); - } -} diff --git a/Mage.Sets/src/mage/cards/g/GisaTheHellraiser.java b/Mage.Sets/src/mage/cards/g/GisaTheHellraiser.java new file mode 100644 index 00000000000..2d91b11f5ad --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GisaTheHellraiser.java @@ -0,0 +1,73 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.CommittedCrimeTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.CompositeCost; +import mage.abilities.costs.common.PayLifeCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.MenaceAbility; +import mage.abilities.keyword.WardAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.game.permanent.token.ZombieRogueToken; + +import java.util.UUID; + +/** + * @author PurpleCrowbar + */ +public final class GisaTheHellraiser extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("Skeletons and Zombies"); + + static { + filter.add(Predicates.or(SubType.SKELETON.getPredicate(), SubType.ZOMBIE.getPredicate())); + } + + public GisaTheHellraiser(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN, SubType.WARLOCK); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Ward--{2}, Pay 2 life. + this.addAbility(new WardAbility(new CompositeCost( + new GenericManaCost(2), new PayLifeCost(2), "{2}, Pay 2 life" + ), false)); + + // Skeletons and Zombies you control get +1/+1 and have menace. + Ability ability = new SimpleStaticAbility(new BoostControlledEffect( + 1, 1, Duration.WhileOnBattlefield, filter, false + )); + ability.addEffect(new GainAbilityControlledEffect( + new MenaceAbility(false), Duration.WhileOnBattlefield, filter + ).setText("and have menace")); + this.addAbility(ability); + + // Whenever you commit a crime, create two tapped 2/2 blue and black Zombie Rogue creature tokens. This ability triggers only once each turn. + ability = new CommittedCrimeTriggeredAbility(new CreateTokenEffect(new ZombieRogueToken(), 2, true)).setTriggersOnceEachTurn(true); + this.addAbility(ability); + } + + private GisaTheHellraiser(final GisaTheHellraiser card) { + super(card); + } + + @Override + public GisaTheHellraiser copy() { + return new GisaTheHellraiser(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GoblinDarkDwellers.java b/Mage.Sets/src/mage/cards/g/GoblinDarkDwellers.java index 62e324b7b27..08edcffffa9 100644 --- a/Mage.Sets/src/mage/cards/g/GoblinDarkDwellers.java +++ b/Mage.Sets/src/mage/cards/g/GoblinDarkDwellers.java @@ -3,11 +3,12 @@ package mage.cards.g; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.effects.common.MayCastTargetThenExileEffect; +import mage.abilities.effects.common.MayCastTargetCardEffect; import mage.abilities.keyword.MenaceAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.CastManaAdjustment; import mage.constants.ComparisonType; import mage.constants.SubType; import mage.filter.common.FilterInstantOrSorceryCard; @@ -39,7 +40,7 @@ public final class GoblinDarkDwellers extends CardImpl { // When Goblin Dark-Dwellers enters the battlefield, you may cast target instant or sorcery card with converted mana cost 3 or less from your graveyard without paying its mana cost. // If that card would be put into your graveyard this turn, exile it instead. - Ability ability = new EntersBattlefieldTriggeredAbility(new MayCastTargetThenExileEffect(true)); + Ability ability = new EntersBattlefieldTriggeredAbility(new MayCastTargetCardEffect(CastManaAdjustment.WITHOUT_PAYING_MANA_COST, true)); ability.addTarget(new TargetCardInYourGraveyard(filter)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/g/GoldPan.java b/Mage.Sets/src/mage/cards/g/GoldPan.java new file mode 100644 index 00000000000..b1abf25a83f --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GoldPan.java @@ -0,0 +1,44 @@ +package mage.cards.g; + +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.CreateTokenEffect; +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.game.permanent.token.TreasureToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GoldPan extends CardImpl { + + public GoldPan(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); + + this.subtype.add(SubType.EQUIPMENT); + + // When Gold Pan enters the battlefield, create a Treasure token. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new TreasureToken()))); + + // Equipped creature gets +1/+1. + this.addAbility(new SimpleStaticAbility(new BoostEquippedEffect(1, 1))); + + // Equip {1} + this.addAbility(new EquipAbility(1)); + } + + private GoldPan(final GoldPan card) { + super(card); + } + + @Override + public GoldPan copy() { + return new GoldPan(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GoldRush.java b/Mage.Sets/src/mage/cards/g/GoldRush.java new file mode 100644 index 00000000000..12c4f56149e --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GoldRush.java @@ -0,0 +1,54 @@ +package mage.cards.g; + +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.MultipliedValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +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.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.filter.common.FilterControlledPermanent; +import mage.game.permanent.token.TreasureToken; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class GoldRush extends CardImpl { + + private static final DynamicValue value = new PermanentsOnBattlefieldCount( + new FilterControlledPermanent(SubType.TREASURE, "Treasure you control") + ); + private static final DynamicValue xValue = new MultipliedValue(value, 2); + private static final Hint hint = new ValueHint("Treasure you control", value); + + + public GoldRush(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{G}"); + + // Create a Treasure token. Until end of turn, up to one target creature gets +2/+2 for each Treasure you control. + this.getSpellAbility().addEffect(new CreateTokenEffect(new TreasureToken())); + this.getSpellAbility().addEffect( + new BoostTargetEffect(xValue, xValue, Duration.EndOfTurn) + .setText("Until end of turn, up to one target creature gets +2/+2 for each Treasure you control.") + ); + this.getSpellAbility().addTarget(new TargetCreaturePermanent(0, 1)); + this.getSpellAbility().addHint(hint); + } + + private GoldRush(final GoldRush card) { + super(card); + } + + @Override + public GoldRush copy() { + return new GoldRush(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GoldveinHydra.java b/Mage.Sets/src/mage/cards/g/GoldveinHydra.java new file mode 100644 index 00000000000..b3d30a3d62b --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GoldveinHydra.java @@ -0,0 +1,64 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.common.DiesSourceTriggeredAbility; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.SourcePermanentPowerCount; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.EntersBattlefieldWithXCountersEffect; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.game.permanent.token.TreasureToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GoldveinHydra extends CardImpl { + + private static final DynamicValue xValue = new SourcePermanentPowerCount(); + + public GoldveinHydra(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{X}{G}"); + + this.subtype.add(SubType.HYDRA); + this.power = new MageInt(0); + this.toughness = new MageInt(0); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // Goldvein Hydra enters the battlefield with X +1/+1 counters on it. + this.addAbility(new EntersBattlefieldAbility( + new EntersBattlefieldWithXCountersEffect(CounterType.P1P1.createInstance()) + )); + + // When Goldvein Hydra dies, create a number of tapped Treasure tokens equal to its power. + this.addAbility(new DiesSourceTriggeredAbility(new CreateTokenEffect( + new TreasureToken(), xValue, true, false + ).setText("create a number of tapped Treasure tokens equal to its power"))); + } + + private GoldveinHydra(final GoldveinHydra card) { + super(card); + } + + @Override + public GoldveinHydra copy() { + return new GoldveinHydra(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GraspOfFate.java b/Mage.Sets/src/mage/cards/g/GraspOfFate.java index 985edb6b32c..20e3cff70d2 100644 --- a/Mage.Sets/src/mage/cards/g/GraspOfFate.java +++ b/Mage.Sets/src/mage/cards/g/GraspOfFate.java @@ -6,13 +6,8 @@ import mage.abilities.effects.common.ExileUntilSourceLeavesEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.FilterPermanent; -import mage.filter.predicate.Predicates; -import mage.filter.predicate.permanent.ControllerIdPredicate; -import mage.game.Game; -import mage.players.Player; -import mage.target.TargetPermanent; -import mage.target.targetadjustment.TargetAdjuster; +import mage.target.common.TargetNonlandPermanent; +import mage.target.targetadjustment.EachOpponentPermanentTargetsAdjuster; import mage.target.targetpointer.EachTargetPointer; import java.util.UUID; @@ -30,7 +25,7 @@ public final class GraspOfFate extends CardImpl { .setTargetPointer(new EachTargetPointer()) .setText("for each opponent, exile up to one target nonland permanent that player controls until {this} leaves the battlefield") ); - ability.setTargetAdjuster(GraspOfFateAdjuster.instance); + ability.setTargetAdjuster(new EachOpponentPermanentTargetsAdjuster(new TargetNonlandPermanent(0,1))); this.addAbility(ability); } @@ -43,23 +38,3 @@ public final class GraspOfFate extends CardImpl { return new GraspOfFate(this); } } - -enum GraspOfFateAdjuster implements TargetAdjuster { - instance; - - @Override - public void adjustTargets(Ability ability, Game game) { - ability.getTargets().clear(); - for (UUID opponentId : game.getOpponents(ability.getControllerId())) { - Player opponent = game.getPlayer(opponentId); - if (opponent == null) { - continue; - } - FilterPermanent filter = new FilterPermanent("nonland permanent from opponent " + opponent.getLogName()); - filter.add(new ControllerIdPredicate(opponentId)); - filter.add(Predicates.not(CardType.LAND.getPredicate())); - TargetPermanent target = new TargetPermanent(0, 1, filter, false); - ability.addTarget(target); - } - } -} diff --git a/Mage.Sets/src/mage/cards/g/GravebreakerLamia.java b/Mage.Sets/src/mage/cards/g/GravebreakerLamia.java index c4914b0d7f1..24127391333 100644 --- a/Mage.Sets/src/mage/cards/g/GravebreakerLamia.java +++ b/Mage.Sets/src/mage/cards/g/GravebreakerLamia.java @@ -40,8 +40,7 @@ public final class GravebreakerLamia extends CardImpl { this.addAbility(LifelinkAbility.getInstance()); // When Gravebreaker Lamia enters the battlefield, search your library for a card, put it into your graveyard, then shuffle your library. - this.addAbility(new EntersBattlefieldTriggeredAbility(new SearchLibraryPutInGraveyardEffect() - .setText("search your library for a card, put it into your graveyard, then shuffle"), false)); + this.addAbility(new EntersBattlefieldTriggeredAbility(new SearchLibraryPutInGraveyardEffect(false), false)); // Spells you cast from your graveyard cost {1} less to cast. this.addAbility(new SimpleStaticAbility(new SpellsCostReductionControllerEffect(filter, 1))); diff --git a/Mage.Sets/src/mage/cards/g/GreatTrainHeist.java b/Mage.Sets/src/mage/cards/g/GreatTrainHeist.java new file mode 100644 index 00000000000..a0150567c5a --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GreatTrainHeist.java @@ -0,0 +1,141 @@ +package mage.cards.g; + +import mage.abilities.Ability; +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.Mode; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.IsPhaseCondition; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.AdditionalCombatPhaseEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.UntapAllEffect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.abilities.keyword.SpreeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.TurnPhase; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.events.DamagedPlayerEvent; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.TreasureToken; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GreatTrainHeist extends CardImpl { + + private static final Condition condition = new IsPhaseCondition(TurnPhase.COMBAT, true); + + public GreatTrainHeist(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{R}"); + + // Spree + this.addAbility(new SpreeAbility(this)); + + // + {2}{R} -- Untap all creatures you control. If it's your combat phase, there is an additional combat phase after this phase. + this.getSpellAbility().addEffect(new UntapAllEffect(StaticFilters.FILTER_CONTROLLED_CREATURES)); + this.getSpellAbility().addEffect(new ConditionalOneShotEffect( + new AdditionalCombatPhaseEffect(), condition, "if it's your combat phase, " + + "there is an additional combat phase after this phase" + )); + this.getSpellAbility().withFirstModeCost(new ManaCostsImpl<>("{2}{R}")); + + // + {2} -- Creatures you control get +1/+0 and gain first strike until end of turn. + this.getSpellAbility().addMode(new Mode(new BoostControlledEffect( + 1, 0, Duration.EndOfTurn + ).setText("creatures you control get +1/+0")) + .addEffect(new GainAbilityControlledEffect( + FirstStrikeAbility.getInstance(), Duration.EndOfTurn, + StaticFilters.FILTER_PERMANENT_CREATURE + ).setText("and gain first strike until end of turn")) + .withCost(new GenericManaCost(2))); + + // + {R} -- Choose target opponent. Whenever a creature you control deals combat damage to that player this turn, create a tapped Treasure token. + this.getSpellAbility().addMode(new Mode(new GreatTrainHeistEffect()) + .addTarget(new TargetOpponent()) + .withCost(new ManaCostsImpl<>("{R}"))); + } + + private GreatTrainHeist(final GreatTrainHeist card) { + super(card); + } + + @Override + public GreatTrainHeist copy() { + return new GreatTrainHeist(this); + } +} + +class GreatTrainHeistEffect extends OneShotEffect { + + GreatTrainHeistEffect() { + super(Outcome.Benefit); + staticText = "choose target opponent. Whenever a creature you control deals combat damage " + + "to that player this turn, create a tapped Treasure token"; + } + + private GreatTrainHeistEffect(final GreatTrainHeistEffect effect) { + super(effect); + } + + @Override + public GreatTrainHeistEffect copy() { + return new GreatTrainHeistEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + game.addDelayedTriggeredAbility(new GreatTrainHeistTriggeredAbility( + getTargetPointer().getFirst(game, source) + ), source); + return true; + } +} + +class GreatTrainHeistTriggeredAbility extends DelayedTriggeredAbility { + + private final UUID playerId; + + GreatTrainHeistTriggeredAbility(UUID playerId) { + super(new CreateTokenEffect(new TreasureToken(), 1, true), Duration.EndOfTurn, false, false); + setTriggerPhrase("Whenever a creature you control deals combat damage to that player this turn, "); + this.playerId = playerId; + } + + private GreatTrainHeistTriggeredAbility(final GreatTrainHeistTriggeredAbility ability) { + super(ability); + this.playerId = ability.playerId; + } + + @Override + public GreatTrainHeistTriggeredAbility copy() { + return new GreatTrainHeistTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DAMAGED_PLAYER; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (!((DamagedPlayerEvent) event).isCombatDamage() || !event.getTargetId().equals(playerId)) { + return false; + } + Permanent permanent = game.getPermanent(event.getSourceId()); + return permanent != null && permanent.isCreature(game) && permanent.isControlledBy(getControllerId()); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GreedsGambit.java b/Mage.Sets/src/mage/cards/g/GreedsGambit.java new file mode 100644 index 00000000000..4c57898d798 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GreedsGambit.java @@ -0,0 +1,57 @@ +package mage.cards.g; + +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfEndStepTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.LeavesBattlefieldTriggeredAbility; +import mage.abilities.effects.common.*; +import mage.abilities.effects.common.discard.DiscardControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.TargetController; +import mage.filter.StaticFilters; +import mage.game.permanent.token.Bat21Token; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GreedsGambit extends CardImpl { + + public GreedsGambit(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{B}"); + + // When Greed's Gambit enters the battlefield, you draw three cards, gain 6 life, and create three 2/1 black Bat creature tokens with flying. + Ability ability = new EntersBattlefieldTriggeredAbility(new DrawCardSourceControllerEffect(3, "you")); + ability.addEffect(new GainLifeEffect(6).setText(", gain 6 life")); + ability.addEffect(new CreateTokenEffect(new Bat21Token(), 3).concatBy(", and")); + this.addAbility(ability); + + // At the beginning of your end step, you discard a card, lose 2 life, and sacrifice a creature. + ability = new BeginningOfEndStepTriggeredAbility(new DiscardControllerEffect(1), TargetController.YOU, false); + ability.addEffect(new LoseLifeSourceControllerEffect(2).setText(", lose 2 life")); + ability.addEffect(new SacrificeControllerEffect( + StaticFilters.FILTER_PERMANENT_A_CREATURE, 1, "" + ).setText(", and sacrifice a creature")); + this.addAbility(ability); + + // When Greed's Gambit leaves the battlefield, you discard three cards, lose 6 life, and sacrifice three creatures. + ability = new LeavesBattlefieldTriggeredAbility(new DiscardControllerEffect(3), false); + ability.addEffect(new LoseLifeSourceControllerEffect(6).setText(", lose 6 life")); + ability.addEffect(new SacrificeControllerEffect( + StaticFilters.FILTER_PERMANENT_CREATURE, 3, "" + ).setText(", and sacrifice 3 creatures")); + this.addAbility(ability); + } + + private GreedsGambit(final GreedsGambit card) { + super(card); + } + + @Override + public GreedsGambit copy() { + return new GreedsGambit(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GroundPounder.java b/Mage.Sets/src/mage/cards/g/GroundPounder.java index f79a9f7b520..7a733d6d0af 100644 --- a/Mage.Sets/src/mage/cards/g/GroundPounder.java +++ b/Mage.Sets/src/mage/cards/g/GroundPounder.java @@ -104,7 +104,7 @@ class GroundPounderTriggeredAbility extends TriggeredAbilityImpl { public boolean checkTrigger(GameEvent event, Game game) { DieRolledEvent drEvent = (DieRolledEvent) event; // silver border card must look for "result" instead "natural result" - return this.isControlledBy(event.getPlayerId()) && drEvent.getResult() >= 5; + return this.isControlledBy(event.getTargetId()) && drEvent.getResult() >= 5; } @Override diff --git a/Mage.Sets/src/mage/cards/g/GruffTriplets.java b/Mage.Sets/src/mage/cards/g/GruffTriplets.java index 1a267762b26..b9501eb5827 100644 --- a/Mage.Sets/src/mage/cards/g/GruffTriplets.java +++ b/Mage.Sets/src/mage/cards/g/GruffTriplets.java @@ -55,7 +55,7 @@ public final class GruffTriplets extends CardImpl { // When Gruff Triplets dies, put a number of +1/+1 counters equal to its power on each creature you control named Gruff Triplets. this.addAbility(new DiesSourceTriggeredAbility( new AddCountersAllEffect( - CounterType.P1P1.createInstance(0), + CounterType.P1P1.createInstance(), new SourcePermanentPowerCount(), filterNamedGruffTriplets ).setText("put a number of +1/+1 counters equal to its power on each creature you control named Gruff Triplets.") diff --git a/Mage.Sets/src/mage/cards/g/GunnerConscript.java b/Mage.Sets/src/mage/cards/g/GunnerConscript.java new file mode 100644 index 00000000000..46245e98259 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GunnerConscript.java @@ -0,0 +1,101 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DiesSourceTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; +import mage.abilities.dynamicvalue.AdditiveDynamicValue; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.AuraAttachedCount; +import mage.abilities.dynamicvalue.common.EquipmentAttachedCount; +import mage.abilities.effects.common.CreateTokenEffect; +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.Zone; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.JunkToken; + +import java.util.Objects; +import java.util.UUID; + +/** + * @author notgreat + */ +public final class GunnerConscript extends CardImpl { + + public GunnerConscript(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.MERCENARY); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Gunner Conscript gets +1/+1 for each Aura and Equipment attached to it. + DynamicValue totalAmount = new AdditiveDynamicValue(new AuraAttachedCount(), new EquipmentAttachedCount()); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, + new BoostSourceEffect(totalAmount, totalAmount, Duration.WhileOnBattlefield) + .setText("{this} gets +1/+1 for each Aura and Equipment attached to it"))); + + // When Gunner Conscript dies, if it was enchanted, create a Junk token. + this.addAbility(new ConditionalInterveningIfTriggeredAbility( + new DiesSourceTriggeredAbility(new CreateTokenEffect(new JunkToken())), GunnerConscriptEnchantedCondition.instance, + "When Gunner Conscript dies, if it was enchanted, create a Junk token.")); + + // When Gunner Conscript dies, if it was equipped, create a Junk token. + this.addAbility(new ConditionalInterveningIfTriggeredAbility( + new DiesSourceTriggeredAbility(new CreateTokenEffect(new JunkToken())), GunnerConscriptEquippedCondition.instance, + "When Gunner Conscript dies, if it was equipped, create a Junk token.")); + } + + private GunnerConscript(final GunnerConscript card) { + super(card); + } + + @Override + public GunnerConscript copy() { + return new GunnerConscript(this); + } +} + + +// Derived from KollTheForgemaster +// Custom predicates call getPermanentOrLKIBattlefield (needed for the death trigger to work correctly) +enum GunnerConscriptEnchantedCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + Permanent sourcePermanent = source.getSourcePermanentOrLKI(game); + return sourcePermanent != null && sourcePermanent.getAttachments() + .stream() + .map(game::getPermanentOrLKIBattlefield) + .filter(Objects::nonNull) + .anyMatch(permanent -> permanent.isEnchantment(game)); + } +} + +enum GunnerConscriptEquippedCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + Permanent sourcePermanent = source.getSourcePermanentOrLKI(game); + return sourcePermanent != null && sourcePermanent.getAttachments() + .stream() + .map(game::getPermanentOrLKIBattlefield) + .filter(Objects::nonNull) + .anyMatch(attachment -> attachment.hasSubtype(SubType.EQUIPMENT, game)); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HaloForager.java b/Mage.Sets/src/mage/cards/h/HaloForager.java index 5f6caabba64..53602eb6b58 100644 --- a/Mage.Sets/src/mage/cards/h/HaloForager.java +++ b/Mage.Sets/src/mage/cards/h/HaloForager.java @@ -8,15 +8,12 @@ import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.costs.mana.ManaCosts; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.MayCastTargetThenExileEffect; +import mage.abilities.effects.common.MayCastTargetCardEffect; import mage.abilities.effects.common.replacement.ThatSpellGraveyardExileReplacementEffect; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.ComparisonType; -import mage.constants.Outcome; -import mage.constants.SubType; +import mage.constants.*; import mage.filter.FilterCard; import mage.filter.common.FilterInstantOrSorceryCard; import mage.filter.predicate.mageobject.ManaValuePredicate; @@ -90,7 +87,10 @@ class HaloForagerPayEffect extends OneShotEffect { "instant or sorcery card with mana value " + costX + " from a graveyard" ); filter.add(new ManaValuePredicate(ComparisonType.EQUAL_TO, costX)); - ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility(new MayCastTargetThenExileEffect(true), false); + ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility( + new MayCastTargetCardEffect(CastManaAdjustment.WITHOUT_PAYING_MANA_COST, true), + false + ); ability.addTarget(new TargetCardInGraveyard(filter)); game.fireReflexiveTriggeredAbility(ability, source); return true; diff --git a/Mage.Sets/src/mage/cards/h/HammerJammer.java b/Mage.Sets/src/mage/cards/h/HammerJammer.java index 34922791698..c0e04eee326 100644 --- a/Mage.Sets/src/mage/cards/h/HammerJammer.java +++ b/Mage.Sets/src/mage/cards/h/HammerJammer.java @@ -110,7 +110,7 @@ class HammerJammerTriggeredAbility extends TriggeredAbilityImpl { DieRolledEvent drEvent = (DieRolledEvent) event; // silver border card must look for "result" instead "natural result" // planar die will trigger it with 0 amount - if (this.isControlledBy(drEvent.getPlayerId())) { + if (this.isControlledBy(drEvent.getTargetId())) { this.getEffects().setValue("rolled", drEvent.getResult()); return true; } diff --git a/Mage.Sets/src/mage/cards/h/HammersOfMoradin.java b/Mage.Sets/src/mage/cards/h/HammersOfMoradin.java index d666b42b2d8..ddab9134d70 100644 --- a/Mage.Sets/src/mage/cards/h/HammersOfMoradin.java +++ b/Mage.Sets/src/mage/cards/h/HammersOfMoradin.java @@ -1,7 +1,6 @@ package mage.cards.h; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.AttacksTriggeredAbility; import mage.abilities.effects.common.TapTargetEffect; import mage.abilities.keyword.MyriadAbility; @@ -9,13 +8,8 @@ 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.permanent.ControllerIdPredicate; -import mage.game.Game; -import mage.players.Player; -import mage.target.TargetPermanent; -import mage.target.targetadjustment.TargetAdjuster; +import mage.target.common.TargetCreaturePermanent; +import mage.target.targetadjustment.EachOpponentPermanentTargetsAdjuster; import mage.target.targetpointer.EachTargetPointer; import java.util.UUID; @@ -41,7 +35,7 @@ public final class HammersOfMoradin extends CardImpl { new TapTargetEffect() .setTargetPointer(new EachTargetPointer()) .setText("for each opponent, tap up to one target creature that player controls") - ).setTargetAdjuster(HammersOfMoradinAdjuster.instance)); + ).setTargetAdjuster(new EachOpponentPermanentTargetsAdjuster(new TargetCreaturePermanent(0,1)))); } private HammersOfMoradin(final HammersOfMoradin card) { @@ -53,22 +47,3 @@ public final class HammersOfMoradin extends CardImpl { return new HammersOfMoradin(this); } } - -enum HammersOfMoradinAdjuster implements TargetAdjuster { - instance; - - @Override - public void adjustTargets(Ability ability, Game game) { - ability.getTargets().clear(); - for (UUID opponentId : game.getOpponents(ability.getControllerId())) { - Player opponent = game.getPlayer(opponentId); - if (opponent == null) { - continue; - } - FilterPermanent filter = new FilterCreaturePermanent("creature controlled by " + opponent.getLogName()); - filter.add(new ControllerIdPredicate(opponentId)); - TargetPermanent target = new TargetPermanent(0, 1, filter); - ability.addTarget(target); - } - } -} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/h/HardbristleBandit.java b/Mage.Sets/src/mage/cards/h/HardbristleBandit.java new file mode 100644 index 00000000000..affcca3ef86 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HardbristleBandit.java @@ -0,0 +1,42 @@ +package mage.cards.h; + +import mage.MageInt; +import mage.abilities.common.CommittedCrimeTriggeredAbility; +import mage.abilities.effects.common.UntapSourceEffect; +import mage.abilities.mana.AnyColorManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HardbristleBandit extends CardImpl { + + public HardbristleBandit(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); + + this.subtype.add(SubType.PLANT); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // {T}: Add one mana of any color. + this.addAbility(new AnyColorManaAbility()); + + // Whenever you commit a crime, untap Hardbristle Bandit. This ability triggers only once each turn. + this.addAbility(new CommittedCrimeTriggeredAbility(new UntapSourceEffect()).setTriggersOnceEachTurn(true)); + } + + private HardbristleBandit(final HardbristleBandit card) { + super(card); + } + + @Override + public HardbristleBandit copy() { + return new HardbristleBandit(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HarrierStrix.java b/Mage.Sets/src/mage/cards/h/HarrierStrix.java new file mode 100644 index 00000000000..e0595ef0cbd --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HarrierStrix.java @@ -0,0 +1,53 @@ +package mage.cards.h; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DrawDiscardControllerEffect; +import mage.abilities.effects.common.TapTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HarrierStrix extends CardImpl { + + public HarrierStrix(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{U}"); + + this.subtype.add(SubType.BIRD); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When Harrier Strix enters the battlefield, tap target permanent. + Ability ability = new EntersBattlefieldTriggeredAbility(new TapTargetEffect()); + ability.addTarget(new TargetPermanent()); + this.addAbility(ability); + + // {2}{U}: Draw a card, then discard a card. + this.addAbility(new SimpleActivatedAbility( + new DrawDiscardControllerEffect(1, 1), new ManaCostsImpl<>("{2}{U}") + )); + } + + private HarrierStrix(final HarrierStrix card) { + super(card); + } + + @Override + public HarrierStrix copy() { + return new HarrierStrix(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HarvesterOfMisery.java b/Mage.Sets/src/mage/cards/h/HarvesterOfMisery.java new file mode 100644 index 00000000000..335e78cf6a3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HarvesterOfMisery.java @@ -0,0 +1,60 @@ +package mage.cards.h; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.DiscardSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.continuous.BoostAllEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +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.Zone; +import mage.filter.StaticFilters; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HarvesterOfMisery extends CardImpl { + + public HarvesterOfMisery(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}{B}"); + + this.subtype.add(SubType.SPIRIT); + this.power = new MageInt(5); + this.toughness = new MageInt(4); + + // Menace + this.addAbility(new MenaceAbility()); + + // When Harvester of Misery enters the battlefield, other creatures get -2/-2 until end of turn. + this.addAbility(new EntersBattlefieldTriggeredAbility(new BoostAllEffect( + -2, -2, Duration.EndOfTurn, StaticFilters.FILTER_PERMANENT_CREATURES, true + ))); + + // {1}{B}, Discard Harvester of Misery: Target creature gets -2/-2 until end of turn. + Ability ability = new SimpleActivatedAbility( + Zone.HAND, new BoostTargetEffect(-2, -2), new ManaCostsImpl<>("{1}{B}") + ); + ability.addCost(new DiscardSourceCost()); + ability.addTarget(new TargetCreaturePermanent()); + this.addAbility(ability); + } + + private HarvesterOfMisery(final HarvesterOfMisery card) { + super(card); + } + + @Override + public HarvesterOfMisery copy() { + return new HarvesterOfMisery(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HeadlinerScarlett.java b/Mage.Sets/src/mage/cards/h/HeadlinerScarlett.java new file mode 100644 index 00000000000..1a8a240d35d --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HeadlinerScarlett.java @@ -0,0 +1,170 @@ +package mage.cards.h; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.combat.CantBlockAllEffect; +import mage.abilities.keyword.HasteAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.ControllerIdPredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetPlayer; +import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class HeadlinerScarlett extends CardImpl { + + public HeadlinerScarlett(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.WARLOCK); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // When Headliner Scarlett enters the battlefield, creatures target player controls can't block this turn. + Ability ability = new EntersBattlefieldTriggeredAbility(new HeadlinerScarlettEntersEffect()); + ability.addTarget(new TargetPlayer()); + this.addAbility(ability); + + // At the beginning of your upkeep, exile the top card of your library face down. You may look at and play that card this turn. + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new HeadlinerScarlettExileEffect(), TargetController.YOU, false)); + } + + private HeadlinerScarlett(final HeadlinerScarlett card) { + super(card); + } + + @Override + public HeadlinerScarlett copy() { + return new HeadlinerScarlett(this); + } +} + +class HeadlinerScarlettEntersEffect extends OneShotEffect { + + HeadlinerScarlettEntersEffect() { + super(Outcome.Detriment); + staticText = "Creatures target player controls can't block this turn."; + } + + private HeadlinerScarlettEntersEffect(final HeadlinerScarlettEntersEffect effect) { + super(effect); + } + + @Override + public HeadlinerScarlettEntersEffect copy() { + return new HeadlinerScarlettEntersEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player targetPlayer = game.getPlayer(source.getFirstTarget()); + if (targetPlayer == null) { + return false; + } + FilterCreaturePermanent filter = new FilterCreaturePermanent("Creatures target player controls"); + filter.add(new ControllerIdPredicate(source.getFirstTarget())); + game.addEffect(new CantBlockAllEffect(filter, Duration.EndOfTurn), source); + return true; + } +} + +class HeadlinerScarlettExileEffect extends OneShotEffect { + + HeadlinerScarlettExileEffect() { + super(Outcome.DrawCard); + staticText = "exile the top card of your library face down. " + + "You may look at and play that card this turn."; + } + + private HeadlinerScarlettExileEffect(final HeadlinerScarlettExileEffect effect) { + super(effect); + } + + @Override + public HeadlinerScarlettExileEffect copy() { + return new HeadlinerScarlettExileEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + Card card = controller.getLibrary().getFromTop(game); + if (card == null) { + return false; + } + + // 1 exile zone per turn + UUID exileId = CardUtil.getExileZoneId("HeadlinerScarlett::" + source.getSourceId() + "::" + game.getTurn(), game); + String exileName = CardUtil.getSourceIdName(game, source) + " turn:" + game.getTurnNum(); + + card.setFaceDown(true, game); + controller.moveCardsToExile(card, source, game, false, exileId, exileName); + if (game.getState().getZone(card.getId()) == Zone.EXILED) { + card.setFaceDown(true, game); + CardUtil.makeCardPlayable(game, source, card, Duration.EndOfTurn, false); + ContinuousEffect effect = new HeadlinerScarlettLookEffect(controller.getId()); + effect.setTargetPointer(new FixedTarget(card, game)); + game.addEffect(effect, source); + } + return true; + } + +} + +class HeadlinerScarlettLookEffect extends AsThoughEffectImpl { + + private final UUID authorizedPlayerId; + + public HeadlinerScarlettLookEffect(UUID authorizedPlayerId) { + super(AsThoughEffectType.LOOK_AT_FACE_DOWN, Duration.EndOfGame, Outcome.Benefit); + this.authorizedPlayerId = authorizedPlayerId; + } + + private HeadlinerScarlettLookEffect(final HeadlinerScarlettLookEffect effect) { + super(effect); + this.authorizedPlayerId = effect.authorizedPlayerId; + } + + @Override + public HeadlinerScarlettLookEffect copy() { + return new HeadlinerScarlettLookEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + UUID cardId = getTargetPointer().getFirst(game, source); + if (cardId == null) { + this.discard(); // card is no longer in the origin zone, effect can be discarded + } + return affectedControllerId.equals(authorizedPlayerId) + && objectId.equals(cardId); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/h/HellToPay.java b/Mage.Sets/src/mage/cards/h/HellToPay.java index 5a034e66356..280b226071e 100644 --- a/Mage.Sets/src/mage/cards/h/HellToPay.java +++ b/Mage.Sets/src/mage/cards/h/HellToPay.java @@ -61,7 +61,7 @@ class HellToPayEffect extends OneShotEffect { } int damage = source.getManaCostsToPay().getX(); int lethal = Math.min(permanent.getLethalDamage(source.getSourceId(), game), damage); - permanent.damage(lethal, source.getSourceId(), source, game); + permanent.damage(damage, source.getSourceId(), source, game); if (damage > lethal) { new TreasureToken().putOntoBattlefield( damage - lethal, game, source, source.getControllerId(), true, false diff --git a/Mage.Sets/src/mage/cards/h/HellspurBrute.java b/Mage.Sets/src/mage/cards/h/HellspurBrute.java new file mode 100644 index 00000000000..59417867a30 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HellspurBrute.java @@ -0,0 +1,58 @@ +package mage.cards.h; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.AffinityEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.mageobject.OutlawPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HellspurBrute extends CardImpl { + + private static final FilterControlledPermanent filter = new FilterControlledPermanent("outlaws"); + + static { + filter.add(OutlawPredicate.instance); + } + + private static final Hint hint = new ValueHint( + "Outlaws you control", new PermanentsOnBattlefieldCount(filter) + ); + + public HellspurBrute(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{R}"); + + this.subtype.add(SubType.MINOTAUR); + this.subtype.add(SubType.MERCENARY); + this.power = new MageInt(5); + this.toughness = new MageInt(4); + + // Affinity for outlaws + this.addAbility(new SimpleStaticAbility(Zone.ALL, new AffinityEffect(filter)).addHint(hint)); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + } + + private HellspurBrute(final HellspurBrute card) { + super(card); + } + + @Override + public HellspurBrute copy() { + return new HellspurBrute(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HellspurPosseBoss.java b/Mage.Sets/src/mage/cards/h/HellspurPosseBoss.java new file mode 100644 index 00000000000..9667b29d803 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HellspurPosseBoss.java @@ -0,0 +1,56 @@ +package mage.cards.h; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.HasteAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.predicate.mageobject.OutlawPredicate; +import mage.game.permanent.token.MercenaryToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HellspurPosseBoss extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("outlaws"); + + static { + filter.add(OutlawPredicate.instance); + } + + public HellspurPosseBoss(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}{R}"); + + this.subtype.add(SubType.LIZARD); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // Other outlaws you control have haste. + this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect( + HasteAbility.getInstance(), Duration.WhileOnBattlefield, filter, true + ))); + + // When Hellspur Posse Boss enters the battlefield, create two 1/1 red Mercenary creature tokens with "{T}: Target creature you control gets +1/+0 until end of turn. Activate only as a sorcery." + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new MercenaryToken(), 2))); + } + + private HellspurPosseBoss(final HellspurPosseBoss card) { + super(card); + } + + @Override + public HellspurPosseBoss copy() { + return new HellspurPosseBoss(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HexgoldSlash.java b/Mage.Sets/src/mage/cards/h/HexgoldSlash.java index 2e38fcc73c0..40db1a9458e 100644 --- a/Mage.Sets/src/mage/cards/h/HexgoldSlash.java +++ b/Mage.Sets/src/mage/cards/h/HexgoldSlash.java @@ -37,7 +37,8 @@ public final class HexgoldSlash extends CardImpl { } class HexgoldSlashEffect extends OneShotEffect { - public HexgoldSlashEffect() { + + HexgoldSlashEffect() { super(Outcome.Damage); staticText = "{this} deals 2 damage to target creature. If that creature has toxic, " + "{this} deals 4 damage to that creature instead"; @@ -49,7 +50,7 @@ class HexgoldSlashEffect extends OneShotEffect { @Override public HexgoldSlashEffect copy() { - return new HexgoldSlashEffect(); + return new HexgoldSlashEffect(this); } @Override diff --git a/Mage.Sets/src/mage/cards/h/HighNoon.java b/Mage.Sets/src/mage/cards/h/HighNoon.java new file mode 100644 index 00000000000..b70b1422b6d --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HighNoon.java @@ -0,0 +1,46 @@ +package mage.cards.h; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.continuous.CantCastMoreThanOneSpellEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.TargetController; +import mage.target.common.TargetAnyTarget; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HighNoon extends CardImpl { + + public HighNoon(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{W}"); + + // Each player can't cast more than one spell each turn. + this.addAbility(new SimpleStaticAbility(new CantCastMoreThanOneSpellEffect(TargetController.ANY))); + + // {4}{R}, Sacrifice High Noon: It deals 5 damage to any target. + Ability ability = new SimpleActivatedAbility( + new DamageTargetEffect(5, "it"), new ManaCostsImpl<>("{4}{R}") + ); + ability.addCost(new SacrificeSourceCost()); + ability.addTarget(new TargetAnyTarget()); + this.addAbility(ability); + } + + private HighNoon(final HighNoon card) { + super(card); + } + + @Override + public HighNoon copy() { + return new HighNoon(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HighwayRobbery.java b/Mage.Sets/src/mage/cards/h/HighwayRobbery.java new file mode 100644 index 00000000000..9f956686764 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HighwayRobbery.java @@ -0,0 +1,46 @@ +package mage.cards.h; + +import mage.abilities.costs.OrCost; +import mage.abilities.costs.common.DiscardCardCost; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.keyword.PlotAbility; +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 HighwayRobbery extends CardImpl { + + public HighwayRobbery(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{R}"); + + // You may discard a card or sacrifice a land. If you do, draw two cards. + this.getSpellAbility().addEffect(new DoIfCostPaid( + new DrawCardSourceControllerEffect(2), + new OrCost( + "discard a card or sacrifice a land", + new DiscardCardCost(), + new SacrificeTargetCost(StaticFilters.FILTER_LAND_A) + ) + )); + + // Plot {1}{R} + this.addAbility(new PlotAbility("{1}{R}")); + } + + private HighwayRobbery(final HighwayRobbery card) { + super(card); + } + + @Override + public HighwayRobbery copy() { + return new HighwayRobbery(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HollowMarauder.java b/Mage.Sets/src/mage/cards/h/HollowMarauder.java new file mode 100644 index 00000000000..5fe86465a55 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HollowMarauder.java @@ -0,0 +1,107 @@ +package mage.cards.h; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HollowMarauder 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 HollowMarauder(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{6}{B}"); + + this.subtype.add(SubType.SPECTER); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(4); + this.toughness = new MageInt(2); + + // 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)); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When Hollow Marauder enters the battlefield, any number of target opponents each discard a card. For each of those opponents who didn't discard a card with mana value 4 or greater, draw a card. + Ability ability = new EntersBattlefieldTriggeredAbility(new HollowMarauderEffect()); + ability.addTarget(new TargetOpponent(0, Integer.MAX_VALUE, false)); + this.addAbility(ability); + } + + private HollowMarauder(final HollowMarauder card) { + super(card); + } + + @Override + public HollowMarauder copy() { + return new HollowMarauder(this); + } +} + +class HollowMarauderEffect extends OneShotEffect { + + HollowMarauderEffect() { + super(Outcome.Benefit); + staticText = "any number of target opponents each discard a card. For each of those opponents " + + "who didn't discard a card with mana value 4 or greater, draw a card"; + } + + private HollowMarauderEffect(final HollowMarauderEffect effect) { + super(effect); + } + + @Override + public HollowMarauderEffect copy() { + return new HollowMarauderEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + int count = 0; + for (UUID targetId : getTargetPointer().getTargets(game, source)) { + Player opponent = game.getPlayer(source.getControllerId()); + if (opponent == null) { + continue; + } + Card card = opponent.discard(1, false, false, source, game).getRandom(game); + if (card == null || card.getManaValue() < 4) { + count++; + } + } + if (count < 1) { + return true; + } + Player player = game.getPlayer(source.getControllerId()); + if (player != null) { + player.drawCards(count, source, game); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/h/HolyCow.java b/Mage.Sets/src/mage/cards/h/HolyCow.java new file mode 100644 index 00000000000..16bbcf72c51 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HolyCow.java @@ -0,0 +1,50 @@ +package mage.cards.h; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.keyword.ScryEffect; +import mage.abilities.keyword.FlashAbility; +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 HolyCow extends CardImpl { + + public HolyCow(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); + + this.subtype.add(SubType.OX); + this.subtype.add(SubType.ANGEL); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When Holy Cow enters the battlefield, you gain 2 life and scry 1. + Ability ability = new EntersBattlefieldTriggeredAbility(new GainLifeEffect(2)); + ability.addEffect(new ScryEffect(1).concatBy("and")); + this.addAbility(ability); + } + + private HolyCow(final HolyCow card) { + super(card); + } + + @Override + public HolyCow copy() { + return new HolyCow(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HonestRutstein.java b/Mage.Sets/src/mage/cards/h/HonestRutstein.java new file mode 100644 index 00000000000..4ae57cc1521 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HonestRutstein.java @@ -0,0 +1,54 @@ +package mage.cards.h; + +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.cost.SpellsCostReductionControllerEffect; +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.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HonestRutstein extends CardImpl { + + private static final FilterCard filter = new FilterCreatureCard("creature spells"); + + public HonestRutstein(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}{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(2); + + // When Honest Rutstein enters the battlefield, return target creature card from your graveyard to your hand. + Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnFromGraveyardToHandTargetEffect()); + ability.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_CREATURE_YOUR_GRAVEYARD)); + this.addAbility(ability); + + // Creature spells you cast cost {1} less to cast. + this.addAbility(new SimpleStaticAbility(new SpellsCostReductionControllerEffect(filter, 1))); + } + + private HonestRutstein(final HonestRutstein card) { + super(card); + } + + @Override + public HonestRutstein copy() { + return new HonestRutstein(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HoodedHydra.java b/Mage.Sets/src/mage/cards/h/HoodedHydra.java index 8483de178c7..c63e5a67216 100644 --- a/Mage.Sets/src/mage/cards/h/HoodedHydra.java +++ b/Mage.Sets/src/mage/cards/h/HoodedHydra.java @@ -49,7 +49,6 @@ public final class HoodedHydra extends CardImpl { // As Hooded Hydra is turned face up, put five +1/+1 counters on it. Effect effect = new AddCountersSourceEffect(CounterType.P1P1.createInstance(5)); effect.setText("put five +1/+1 counters on it"); - // TODO: Does not work because the ability is still removed from permanent while the effect checks if the ability still exists. Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new AsTurnedFaceUpEffect(effect, false)); ability.setWorksFaceDown(true); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/h/HostileInvestigator.java b/Mage.Sets/src/mage/cards/h/HostileInvestigator.java new file mode 100644 index 00000000000..c474d559a80 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HostileInvestigator.java @@ -0,0 +1,50 @@ +package mage.cards.h; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.DiscardCardPlayerTriggeredAbility; +import mage.abilities.effects.common.discard.DiscardTargetEffect; +import mage.abilities.effects.keyword.InvestigateEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HostileInvestigator extends CardImpl { + + public HostileInvestigator(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}"); + + this.subtype.add(SubType.OGRE); + this.subtype.add(SubType.ROGUE); + this.subtype.add(SubType.DETECTIVE); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // When Hostile Investigator enters the battlefield, target opponent discards a card. + Ability ability = new EntersBattlefieldTriggeredAbility(new DiscardTargetEffect(1)); + ability.addTarget(new TargetOpponent()); + this.addAbility(ability); + + // Whenever one or more players discard one or more cards, investigate. This ability triggers only once each turn. + this.addAbility(new DiscardCardPlayerTriggeredAbility(new InvestigateEffect(), false) + .setTriggerPhrase("Whenever one or more players discard one or more cards, ") + .setTriggersOnceEachTurn(true)); + } + + private HostileInvestigator(final HostileInvestigator card) { + super(card); + } + + @Override + public HostileInvestigator copy() { + return new HostileInvestigator(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HowlpackAvenger.java b/Mage.Sets/src/mage/cards/h/HowlpackAvenger.java index 7e56790709b..de1d6947b17 100644 --- a/Mage.Sets/src/mage/cards/h/HowlpackAvenger.java +++ b/Mage.Sets/src/mage/cards/h/HowlpackAvenger.java @@ -15,7 +15,7 @@ import mage.constants.Duration; import mage.constants.SubType; import mage.constants.Zone; import mage.game.Game; -import mage.game.events.DamagedBatchEvent; +import mage.game.events.DamagedBatchForPermanentsEvent; import mage.game.events.GameEvent; import mage.target.common.TargetAnyTarget; @@ -81,8 +81,7 @@ class HowlpackAvengerTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { - DamagedBatchEvent dEvent = (DamagedBatchEvent) event; - int damage = dEvent + int damage = ((DamagedBatchForPermanentsEvent) event) .getEvents() .stream() .filter(damagedEvent -> isControlledBy(game.getControllerId(damagedEvent.getTargetId()))) diff --git a/Mage.Sets/src/mage/cards/h/HuntedBonebrute.java b/Mage.Sets/src/mage/cards/h/HuntedBonebrute.java index be01ab03dd5..8a92007203c 100644 --- a/Mage.Sets/src/mage/cards/h/HuntedBonebrute.java +++ b/Mage.Sets/src/mage/cards/h/HuntedBonebrute.java @@ -32,7 +32,7 @@ public final class HuntedBonebrute extends CardImpl { this.toughness = new MageInt(2); // Menace - this.addAbility(new MenaceAbility()); + this.addAbility(new MenaceAbility(false)); // When Hunted Bonebrute enters the battlefield, target opponent creates two 1/1 white Dog creature tokens. Ability ability = new EntersBattlefieldTriggeredAbility(new CreateTokenTargetEffect(new WhiteDogToken(), 2)); diff --git a/Mage.Sets/src/mage/cards/i/IanTheReckless.java b/Mage.Sets/src/mage/cards/i/IanTheReckless.java new file mode 100644 index 00000000000..e1bac548138 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/IanTheReckless.java @@ -0,0 +1,62 @@ +package mage.cards.i; + +import mage.MageInt; +import mage.abilities.TriggeredAbility; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.SourceMatchesFilterCondition; +import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; +import mage.abilities.dynamicvalue.common.SourcePermanentPowerCount; +import mage.abilities.effects.common.DamageControllerEffect; +import mage.abilities.effects.common.DamageTargetEffect; +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.predicate.permanent.ModifiedPredicate; +import mage.target.common.TargetAnyTarget; + +import java.util.UUID; + +/** + * @author notgreat + */ +public final class IanTheReckless extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent(); + + static { + filter.add(ModifiedPredicate.instance); + } + + private static final Condition condition = new SourceMatchesFilterCondition(filter); + + public IanTheReckless(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.WARRIOR); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // Whenever Ian the Reckless attacks, if it's modified, you may have it deal damage equal to its power to you and any target. + TriggeredAbility ability = new AttacksTriggeredAbility(new DamageControllerEffect( + new SourcePermanentPowerCount()).setText("have it deal damage equal to its power to you"), true); + ability.addEffect(new DamageTargetEffect(new SourcePermanentPowerCount()).setText("and any target")); + ability.addTarget(new TargetAnyTarget()); + this.addAbility(new ConditionalInterveningIfTriggeredAbility(ability, condition, + "Whenever {this} attacks, if it's modified, you may have it deal damage equal to its power to you and any target.")); + } + + private IanTheReckless(final IanTheReckless card) { + super(card); + } + + @Override + public IanTheReckless copy() { + return new IanTheReckless(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/InTheDarknessBindThem.java b/Mage.Sets/src/mage/cards/i/InTheDarknessBindThem.java index 361c8081536..c890b6a5139 100644 --- a/Mage.Sets/src/mage/cards/i/InTheDarknessBindThem.java +++ b/Mage.Sets/src/mage/cards/i/InTheDarknessBindThem.java @@ -1,6 +1,5 @@ package mage.cards.i; -import mage.abilities.Ability; import mage.abilities.common.SagaAbility; import mage.abilities.effects.Effects; import mage.abilities.effects.common.CreateTokenEffect; @@ -15,14 +14,9 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SagaChapter; import mage.constants.SubType; -import mage.filter.FilterPermanent; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.permanent.ControllerIdPredicate; -import mage.game.Game; import mage.game.permanent.token.WraithToken; -import mage.players.Player; -import mage.target.TargetPermanent; -import mage.target.targetadjustment.TargetAdjuster; +import mage.target.common.TargetCreaturePermanent; +import mage.target.targetadjustment.EachOpponentPermanentTargetsAdjuster; import mage.target.targetpointer.EachTargetPointer; import java.util.UUID; @@ -63,7 +57,7 @@ public final class InTheDarknessBindThem extends CardImpl { ability.addEffect(new TheRingTemptsYouEffect()); ability.getEffects().setTargetPointer(new EachTargetPointer()); - ability.setTargetAdjuster(InTheDarknessBindThemAdjuster.instance); + ability.setTargetAdjuster(new EachOpponentPermanentTargetsAdjuster(new TargetCreaturePermanent(0,1))); } ); @@ -79,21 +73,3 @@ public final class InTheDarknessBindThem extends CardImpl { return new InTheDarknessBindThem(this); } } - -enum InTheDarknessBindThemAdjuster implements TargetAdjuster { - instance; - - @Override - public void adjustTargets(Ability ability, Game game) { - ability.getTargets().clear(); - for (UUID opponentId : game.getOpponents(ability.getControllerId())) { - Player player = game.getPlayer(opponentId); - if (player == null) { - continue; - } - FilterPermanent filter = new FilterCreaturePermanent("creature controlled by " + player.getName()); - filter.add(new ControllerIdPredicate(opponentId)); - ability.addTarget(new TargetPermanent(0, 1, filter)); - } - } -} diff --git a/Mage.Sets/src/mage/cards/i/InnocentBystander.java b/Mage.Sets/src/mage/cards/i/InnocentBystander.java index f88a5cd37ac..6efdbc1f578 100644 --- a/Mage.Sets/src/mage/cards/i/InnocentBystander.java +++ b/Mage.Sets/src/mage/cards/i/InnocentBystander.java @@ -59,7 +59,7 @@ class InnocentBystanderTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT; } @Override diff --git a/Mage.Sets/src/mage/cards/i/InnocuousResearcher.java b/Mage.Sets/src/mage/cards/i/InnocuousResearcher.java new file mode 100644 index 00000000000..715551b2c89 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InnocuousResearcher.java @@ -0,0 +1,94 @@ +package mage.cards.i; + +import java.util.UUID; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.BeginningOfEndStepTriggeredAbility; +import mage.abilities.dynamicvalue.common.ParleyCount; +import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; +import mage.abilities.effects.common.DrawCardAllEffect; +import mage.abilities.effects.common.UntapAllLandsControllerEffect; +import mage.abilities.effects.keyword.InvestigateEffect; +import mage.constants.*; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.game.Game; +import mage.game.events.GameEvent; + +/** + * @author Cguy7777 + */ +public final class InnocuousResearcher extends CardImpl { + + public InnocuousResearcher(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}"); + + this.subtype.add(SubType.CENTAUR); + this.subtype.add(SubType.DETECTIVE); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // Parley -- Whenever Innocuous Researcher attacks, each player reveals the top card of their library. + // For each nonland card revealed this way, you investigate. Then each player draws a card. + Ability parleyAbility = new AttacksTriggeredAbility(new InvestigateEffect(ParleyCount.getInstance()) + .setText("each player reveals the top card of their library. " + + "For each nonland card revealed this way, you investigate")); + parleyAbility.addEffect(new DrawCardAllEffect(1).concatBy("Then")); + this.addAbility(parleyAbility.setAbilityWord(AbilityWord.PARLEY)); + + // At the beginning of your end step, you may untap all lands you control. + // If you do, you can't cast spells until your next turn. + Ability untapAbility = new BeginningOfEndStepTriggeredAbility( + new UntapAllLandsControllerEffect(), TargetController.YOU, true); + untapAbility.addEffect(new InnocuousResearcherEffect().concatBy("If you do,")); + this.addAbility(untapAbility); + } + + private InnocuousResearcher(final InnocuousResearcher card) { + super(card); + } + + @Override + public InnocuousResearcher copy() { + return new InnocuousResearcher(this); + } +} + +class InnocuousResearcherEffect extends ContinuousRuleModifyingEffectImpl { + + InnocuousResearcherEffect() { + super(Duration.UntilYourNextTurn, Outcome.PreventCast); + staticText = "you can't cast spells until your next turn"; + } + + private InnocuousResearcherEffect(final InnocuousResearcherEffect effect) { + super(effect); + } + + @Override + public InnocuousResearcherEffect copy() { + return new InnocuousResearcherEffect(this); + } + + @Override + public String getInfoMessage(Ability source, GameEvent event, Game game) { + MageObject mageObject = game.getObject(source); + if (mageObject != null) { + return "You can't cast spells until your next turn (" + mageObject.getIdName() + ")."; + } + return null; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.CAST_SPELL; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + return source.isControlledBy(event.getPlayerId()); + } +} diff --git a/Mage.Sets/src/mage/cards/i/InsatiableAvarice.java b/Mage.Sets/src/mage/cards/i/InsatiableAvarice.java new file mode 100644 index 00000000000..8c404e52275 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InsatiableAvarice.java @@ -0,0 +1,48 @@ +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.DrawCardTargetEffect; +import mage.abilities.effects.common.LoseLifeTargetEffect; +import mage.abilities.effects.common.search.SearchLibraryPutOnLibraryEffect; +import mage.abilities.keyword.SpreeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.target.TargetPlayer; +import mage.target.common.TargetCardInLibrary; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class InsatiableAvarice extends CardImpl { + + public InsatiableAvarice(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{B}"); + + // Spree + this.addAbility(new SpreeAbility(this)); + + // + {2} -- Search your library for a card, then shuffle and put that card on top. + this.getSpellAbility().addEffect(new SearchLibraryPutOnLibraryEffect(new TargetCardInLibrary(), false)); + this.getSpellAbility().withFirstModeCost(new GenericManaCost(2)); + + // + {B}{B} -- Target player draws three cards and loses 3 life. + this.getSpellAbility().addMode(new Mode(new DrawCardTargetEffect(3)) + .addEffect(new LoseLifeTargetEffect(3).setText("and loses 3 life")) + .addTarget(new TargetPlayer()) + .withCost(new ManaCostsImpl<>("{B}{B}"))); + } + + private InsatiableAvarice(final InsatiableAvarice card) { + super(card); + } + + @Override + public InsatiableAvarice copy() { + return new InsatiableAvarice(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/IntimidationCampaign.java b/Mage.Sets/src/mage/cards/i/IntimidationCampaign.java new file mode 100644 index 00000000000..a0b0fba1a72 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/IntimidationCampaign.java @@ -0,0 +1,44 @@ +package mage.cards.i; + +import mage.abilities.Ability; +import mage.abilities.common.CommittedCrimeTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.LoseLifeOpponentsEffect; +import mage.abilities.effects.common.ReturnToHandSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class IntimidationCampaign extends CardImpl { + + public IntimidationCampaign(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{U}{B}"); + + // When Intimidation Campaign enters the battlefield, each opponent loses 1 life, you gain 1 life, and you draw a card. + Ability ability = new EntersBattlefieldTriggeredAbility(new LoseLifeOpponentsEffect(1)); + ability.addEffect(new GainLifeEffect(1).concatBy(",")); + ability.addEffect(new DrawCardSourceControllerEffect(1).concatBy(", and you")); + this.addAbility(ability); + + // Whenever you commit a crime, you may return Intimidation Campaign to its owner's hand. + this.addAbility(new CommittedCrimeTriggeredAbility( + new ReturnToHandSourceEffect(true), true + )); + } + + private IntimidationCampaign(final IntimidationCampaign card) { + super(card); + } + + @Override + public IntimidationCampaign copy() { + return new IntimidationCampaign(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/IntrepidStablemaster.java b/Mage.Sets/src/mage/cards/i/IntrepidStablemaster.java new file mode 100644 index 00000000000..900bcc0e466 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/IntrepidStablemaster.java @@ -0,0 +1,58 @@ +package mage.cards.i; + +import mage.MageInt; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.keyword.ReachAbility; +import mage.abilities.mana.ConditionalAnyColorManaAbility; +import mage.abilities.mana.GreenManaAbility; +import mage.abilities.mana.conditional.ConditionalSpellManaBuilder; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterSpell; +import mage.filter.predicate.Predicates; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class IntrepidStablemaster extends CardImpl { + + private static final FilterSpell filter = new FilterSpell("Mount or Vehicle spells"); + + static { + filter.add(Predicates.or(SubType.MOUNT.getPredicate(), SubType.VEHICLE.getPredicate())); + } + + public IntrepidStablemaster(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SCOUT); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // {T}: Add {G}. + this.addAbility(new GreenManaAbility()); + + // {T}: Add two mana of any one color. Spend this mana only to cast Mount or Vehicle spells. + this.addAbility(new ConditionalAnyColorManaAbility( + new TapSourceCost(), 2, + new ConditionalSpellManaBuilder(filter), true + )); + } + + private IntrepidStablemaster(final IntrepidStablemaster card) { + super(card); + } + + @Override + public IntrepidStablemaster copy() { + return new IntrepidStablemaster(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/InventiveWingsmith.java b/Mage.Sets/src/mage/cards/i/InventiveWingsmith.java new file mode 100644 index 00000000000..592978c318f --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InventiveWingsmith.java @@ -0,0 +1,54 @@ +package mage.cards.i; + +import mage.MageInt; +import mage.abilities.common.BeginningOfEndStepTriggeredAbility; +import mage.abilities.condition.CompoundCondition; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.HaventCastSpellFromHandThisTurnCondition; +import mage.abilities.condition.common.SourceHasCounterCondition; +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.TargetController; +import mage.constants.Zone; +import mage.counters.CounterType; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class InventiveWingsmith extends CardImpl { + + private static final Condition condition = new CompoundCondition( + "if you haven't cast a spell from your hand this turn and {this} doesn't have a flying counter on it", + HaventCastSpellFromHandThisTurnCondition.instance, + new SourceHasCounterCondition(CounterType.FLYING, 0, 0) + ); + + public InventiveWingsmith(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); + + this.subtype.add(SubType.DWARF); + this.subtype.add(SubType.ARTIFICER); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // At the beginning of your end step, if you haven't cast a spell from your hand this turn and Inventive Wingsmith doesn't have a flying counter on it, put a flying counter on it. + this.addAbility(new BeginningOfEndStepTriggeredAbility( + Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.FLYING.createInstance()), + TargetController.YOU, condition, false + ).withRuleTextReplacement(true).addHint(HaventCastSpellFromHandThisTurnCondition.hint)); + } + + private InventiveWingsmith(final InventiveWingsmith card) { + super(card); + } + + @Override + public InventiveWingsmith copy() { + return new InventiveWingsmith(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/IrascibleWolverine.java b/Mage.Sets/src/mage/cards/i/IrascibleWolverine.java new file mode 100644 index 00000000000..24bc28ea2de --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/IrascibleWolverine.java @@ -0,0 +1,45 @@ +package mage.cards.i; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.ExileTopXMayPlayUntilEffect; +import mage.abilities.keyword.PlotAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class IrascibleWolverine extends CardImpl { + + public IrascibleWolverine(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); + + this.subtype.add(SubType.WOLVERINE); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // When Irascible Wolverine enters the battlefield, exile the top card of your library. Until end of turn, you may play that card. + this.addAbility(new EntersBattlefieldTriggeredAbility( + new ExileTopXMayPlayUntilEffect(1, Duration.EndOfTurn) + .withTextOptions("that card", false) + )); + + // Plot {2}{R} + this.addAbility(new PlotAbility("{2}{R}")); + } + + private IrascibleWolverine(final IrascibleWolverine card) { + super(card); + } + + @Override + public IrascibleWolverine copy() { + return new IrascibleWolverine(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/IronFistPulverizer.java b/Mage.Sets/src/mage/cards/i/IronFistPulverizer.java new file mode 100644 index 00000000000..b6230b62412 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/IronFistPulverizer.java @@ -0,0 +1,48 @@ +package mage.cards.i; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.CastSecondSpellTriggeredAbility; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.keyword.ScryEffect; +import mage.abilities.keyword.ReachAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class IronFistPulverizer extends CardImpl { + + public IronFistPulverizer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{R}"); + + this.subtype.add(SubType.GIANT); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(4); + this.toughness = new MageInt(5); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // Whenever you cast your second spell each turn, Iron-Fist Pulverizer deals 2 damage to target opponent. Scry 1. + Ability ability = new CastSecondSpellTriggeredAbility(new DamageTargetEffect(2)); + ability.addEffect(new ScryEffect(1)); + ability.addTarget(new TargetOpponent()); + this.addAbility(ability); + } + + private IronFistPulverizer(final IronFistPulverizer card) { + super(card); + } + + @Override + public IronFistPulverizer copy() { + return new IronFistPulverizer(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/IsuTheAbominable.java b/Mage.Sets/src/mage/cards/i/IsuTheAbominable.java index 9797bb855b6..0b4c0b05c51 100644 --- a/Mage.Sets/src/mage/cards/i/IsuTheAbominable.java +++ b/Mage.Sets/src/mage/cards/i/IsuTheAbominable.java @@ -7,14 +7,13 @@ import mage.abilities.costs.OrCost; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.DoIfCostPaid; import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; -import mage.abilities.effects.common.continuous.PlayTheTopCardEffect; +import mage.abilities.effects.common.continuous.PlayFromTopOfLibraryEffect; 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.TargetController; import mage.counters.CounterType; import mage.filter.FilterCard; import mage.filter.FilterPermanent; @@ -49,9 +48,7 @@ public final class IsuTheAbominable extends CardImpl { this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); // You may play snow lands and cast snow spells from the top of your library. - this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect( - TargetController.YOU, filter, false - ))); + this.addAbility(new SimpleStaticAbility(new PlayFromTopOfLibraryEffect(filter))); // Whenever another snow permanent enters the battlefield under your control, you may pay {G}, {W}, or {U}. If you do, put a +1/+1 counter on Isu the Abominable. this.addAbility(new EntersBattlefieldControlledTriggeredAbility( diff --git a/Mage.Sets/src/mage/cards/j/JaceCunningCastaway.java b/Mage.Sets/src/mage/cards/j/JaceCunningCastaway.java index 24ece90b229..fbcadcb0dab 100644 --- a/Mage.Sets/src/mage/cards/j/JaceCunningCastaway.java +++ b/Mage.Sets/src/mage/cards/j/JaceCunningCastaway.java @@ -12,7 +12,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.game.Game; -import mage.game.events.DamagedPlayerEvent; +import mage.game.events.DamagedBatchForOnePlayerEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.permanent.token.JaceCunningCastawayIllusionToken; @@ -98,26 +98,21 @@ class JaceCunningCastawayDamageTriggeredAbility extends DelayedTriggeredAbility @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PLAYER - || event.getType() == GameEvent.EventType.COMBAT_DAMAGE_STEP_POST; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PLAYER; } @Override public boolean checkTrigger(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.DAMAGED_PLAYER) { - if (((DamagedPlayerEvent) event).isCombatDamage()) { - Permanent creature = game.getPermanent(event.getSourceId()); - if (creature != null && creature.isControlledBy(controllerId) - && !damagedPlayerIds.contains(event.getTargetId())) { - damagedPlayerIds.add(event.getTargetId()); - return true; - } - } - } - if (event.getType() == GameEvent.EventType.COMBAT_DAMAGE_STEP_POST) { - damagedPlayerIds.clear(); - } - return false; + + DamagedBatchForOnePlayerEvent dEvent = (DamagedBatchForOnePlayerEvent) event; + + int damageFromYours = dEvent.getEvents() + .stream() + .filter(ev -> ev.getSourceId().equals(controllerId)) + .mapToInt(GameEvent::getAmount) + .sum(); + + return dEvent.isCombatDamage() && damageFromYours > 0; } @Override diff --git a/Mage.Sets/src/mage/cards/j/JaceReawakened.java b/Mage.Sets/src/mage/cards/j/JaceReawakened.java new file mode 100644 index 00000000000..c77ae896001 --- /dev/null +++ b/Mage.Sets/src/mage/cards/j/JaceReawakened.java @@ -0,0 +1,145 @@ +package mage.cards.j; + +import mage.abilities.Ability; +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.LoyaltyAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; +import mage.abilities.effects.common.CopyTargetSpellEffect; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.effects.common.DrawDiscardControllerEffect; +import mage.abilities.effects.common.MayExileCardFromHandPlottedEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.ManaValuePredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.stack.Spell; +import mage.players.Player; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class JaceReawakened extends CardImpl { + + private static final FilterCard filter = new FilterCard("nonland card with mana value 3 or less"); + + static { + filter.add(Predicates.not(CardType.LAND.getPredicate())); + filter.add(new ManaValuePredicate(ComparisonType.OR_LESS, 3)); + } + + public JaceReawakened(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{U}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.JACE); + this.setStartingLoyalty(3); + + // You can't cast this spell during your first, second, or third turns of the game. + this.addAbility(new SimpleStaticAbility(Zone.ALL, new CantCastJaceReawakenedEffect())); + + // +1: Draw a card, then discard a card. + this.addAbility(new LoyaltyAbility(new DrawDiscardControllerEffect(1, 1), 1)); + + // +1: You may exile a nonland card with mana value 3 or less from your hand. If you do, it becomes plotted. + this.addAbility(new LoyaltyAbility(new MayExileCardFromHandPlottedEffect(filter), 1)); + + // -6: Until end of turn, whenever you cast a spell, copy it. You may choose new targets for the copy. + this.addAbility(new LoyaltyAbility( + new CreateDelayedTriggeredAbilityEffect( + new JaceReawakenedDelayedTriggeredAbility() + ), -6 + )); + } + + private JaceReawakened(final JaceReawakened card) { + super(card); + } + + @Override + public JaceReawakened copy() { + return new JaceReawakened(this); + } +} + +/** + * Same as {@link mage.cards.s.SerraAvenger Serra Avenger} + */ +class CantCastJaceReawakenedEffect extends ContinuousRuleModifyingEffectImpl { + + CantCastJaceReawakenedEffect() { + super(Duration.WhileOnBattlefield, Outcome.Detriment); + staticText = "You can't cast this spell during your first, second, or third turns of the game"; + } + + private CantCastJaceReawakenedEffect(final CantCastJaceReawakenedEffect effect) { + super(effect); + } + + @Override + public CantCastJaceReawakenedEffect copy() { + return new CantCastJaceReawakenedEffect(this); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.CAST_SPELL; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + if (event.getSourceId().equals(source.getSourceId())) { + Player controller = game.getPlayer(source.getControllerId()); + // it can be cast on other players turn 1 - 3 if some effect let allow you to do this + if (controller != null && controller.getTurns() <= 3 && game.isActivePlayer(source.getControllerId())) { + return true; + } + } + return false; + } +} + +class JaceReawakenedDelayedTriggeredAbility extends DelayedTriggeredAbility { + + JaceReawakenedDelayedTriggeredAbility() { + super(new CopyTargetSpellEffect(true), Duration.EndOfTurn, false); + } + + private JaceReawakenedDelayedTriggeredAbility(final JaceReawakenedDelayedTriggeredAbility ability) { + super(ability); + } + + @Override + public JaceReawakenedDelayedTriggeredAbility copy() { + return new JaceReawakenedDelayedTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.SPELL_CAST; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (event.getPlayerId().equals(this.getControllerId())) { + Spell spell = game.getStack().getSpell(event.getTargetId()); + if (spell != null) { + this.getEffects().setTargetPointer(new FixedTarget(event.getTargetId())); + return true; + } + } + return false; + } + + @Override + public String getRule() { + return "Until end of turn, whenever you cast a spell, copy it. You may choose new targets for the copy."; + } +} diff --git a/Mage.Sets/src/mage/cards/j/JaceTelepathUnbound.java b/Mage.Sets/src/mage/cards/j/JaceTelepathUnbound.java index baac701e358..bcf3a7cd88e 100644 --- a/Mage.Sets/src/mage/cards/j/JaceTelepathUnbound.java +++ b/Mage.Sets/src/mage/cards/j/JaceTelepathUnbound.java @@ -1,11 +1,10 @@ package mage.cards.j; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.LoyaltyAbility; -import mage.abilities.effects.*; +import mage.abilities.effects.Effect; import mage.abilities.effects.common.GetEmblemEffect; -import mage.abilities.effects.common.MayCastTargetThenExileEffect; +import mage.abilities.effects.common.MayCastTargetCardEffect; import mage.abilities.effects.common.continuous.BoostTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -18,8 +17,9 @@ import mage.game.command.emblems.JaceTelepathUnboundEmblem; import mage.target.common.TargetCardInYourGraveyard; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class JaceTelepathUnbound extends CardImpl { @@ -42,7 +42,7 @@ public final class JaceTelepathUnbound extends CardImpl { this.addAbility(ability); // -3: You may cast target instant or sorcery card from your graveyard this turn. If that card would be put into your graveyard this turn, exile it instead. - ability = new LoyaltyAbility(new MayCastTargetThenExileEffect(Duration.EndOfTurn), -3); + ability = new LoyaltyAbility(new MayCastTargetCardEffect(Duration.EndOfTurn, true), -3); ability.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY_FROM_YOUR_GRAVEYARD)); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/j/JaggedBarrens.java b/Mage.Sets/src/mage/cards/j/JaggedBarrens.java new file mode 100644 index 00000000000..bdb94412fff --- /dev/null +++ b/Mage.Sets/src/mage/cards/j/JaggedBarrens.java @@ -0,0 +1,48 @@ +package mage.cards.j; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTappedAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.mana.BlackManaAbility; +import mage.abilities.mana.RedManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class JaggedBarrens extends CardImpl { + + public JaggedBarrens(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + this.subtype.add(SubType.DESERT); + + // Jagged Barrens enters the battlefield tapped. + this.addAbility(new EntersBattlefieldTappedAbility()); + + // When Jagged Barrens enters the battlefield, it deals 1 damage to target opponent. + Ability ability = new EntersBattlefieldTriggeredAbility(new DamageTargetEffect(1, "it")); + ability.addTarget(new TargetOpponent()); + this.addAbility(ability); + + // {T}: Add {B} or {R}. + this.addAbility(new BlackManaAbility()); + this.addAbility(new RedManaAbility()); + } + + private JaggedBarrens(final JaggedBarrens card) { + super(card); + } + + @Override + public JaggedBarrens copy() { + return new JaggedBarrens(this); + } +} diff --git a/Mage.Sets/src/mage/cards/j/JailbreakScheme.java b/Mage.Sets/src/mage/cards/j/JailbreakScheme.java new file mode 100644 index 00000000000..3743ea6470e --- /dev/null +++ b/Mage.Sets/src/mage/cards/j/JailbreakScheme.java @@ -0,0 +1,50 @@ +package mage.cards.j; + +import mage.abilities.Mode; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.PutOnTopOrBottomLibraryTargetEffect; +import mage.abilities.effects.common.combat.CantBeBlockedTargetEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.keyword.SpreeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class JailbreakScheme extends CardImpl { + + public JailbreakScheme(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{U}"); + + // Spree + this.addAbility(new SpreeAbility(this)); + + // + {3} -- Put a +1/+1 counter on target creature. It can't be blocked this turn. + this.getSpellAbility().addEffect(new AddCountersTargetEffect(CounterType.P1P1.createInstance())); + this.getSpellAbility().addEffect(new CantBeBlockedTargetEffect().setText("it can't be blocked this turn")); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + this.getSpellAbility().withFirstModeCost(new GenericManaCost(3)); + + // + {2} -- Target artifact or creature's owner puts it on the top or bottom of their library. + this.getSpellAbility().addMode(new Mode(new PutOnTopOrBottomLibraryTargetEffect(false)) + .addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_ARTIFACT_OR_CREATURE)) + .withCost(new GenericManaCost(2))); + } + + private JailbreakScheme(final JailbreakScheme card) { + super(card); + } + + @Override + public JailbreakScheme copy() { + return new JailbreakScheme(this); + } +} diff --git a/Mage.Sets/src/mage/cards/j/JemLightfooteSkyExplorer.java b/Mage.Sets/src/mage/cards/j/JemLightfooteSkyExplorer.java new file mode 100644 index 00000000000..7b57e037dac --- /dev/null +++ b/Mage.Sets/src/mage/cards/j/JemLightfooteSkyExplorer.java @@ -0,0 +1,50 @@ +package mage.cards.j; + +import mage.MageInt; +import mage.abilities.common.BeginningOfEndStepTriggeredAbility; +import mage.abilities.condition.common.HaventCastSpellFromHandThisTurnCondition; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class JemLightfooteSkyExplorer extends CardImpl { + + public JemLightfooteSkyExplorer(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.SCOUT); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // At the beginning of your end step, if you haven't cast a spell from your hand this turn, draw a card. + this.addAbility(new BeginningOfEndStepTriggeredAbility( + Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1), + TargetController.YOU, HaventCastSpellFromHandThisTurnCondition.instance, false + ).addHint(HaventCastSpellFromHandThisTurnCondition.hint)); + } + + private JemLightfooteSkyExplorer(final JemLightfooteSkyExplorer card) { + super(card); + } + + @Override + public JemLightfooteSkyExplorer copy() { + return new JemLightfooteSkyExplorer(this); + } +} diff --git a/Mage.Sets/src/mage/cards/j/Jilt.java b/Mage.Sets/src/mage/cards/j/Jilt.java index c71e80de3af..a3fff63cea4 100644 --- a/Mage.Sets/src/mage/cards/j/Jilt.java +++ b/Mage.Sets/src/mage/cards/j/Jilt.java @@ -1,10 +1,8 @@ package mage.cards.j; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.condition.common.KickedCondition; import mage.abilities.decorator.ConditionalOneShotEffect; -import mage.abilities.effects.Effect; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.ReturnToHandTargetEffect; import mage.abilities.keyword.KickerAbility; @@ -14,11 +12,12 @@ import mage.constants.CardType; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.other.AnotherTargetPredicate; import mage.game.Game; -import mage.target.Target; import mage.target.common.TargetCreaturePermanent; import mage.target.targetadjustment.TargetAdjuster; import mage.target.targetpointer.SecondTargetPointer; +import java.util.UUID; + /** * * @author fireshoes @@ -33,15 +32,12 @@ public final class Jilt extends CardImpl { // Return target creature to its owner's hand. If Jilt was kicked, it deals 2 damage to another target creature. this.getSpellAbility().addEffect(new ReturnToHandTargetEffect()); - Effect effect = new ConditionalOneShotEffect( + this.getSpellAbility().addEffect(new ConditionalOneShotEffect( new DamageTargetEffect(2, "it"), KickedCondition.ONCE, - "if this spell was kicked, it deals 2 damage to another target creature"); - effect.setTargetPointer(new SecondTargetPointer()); - this.getSpellAbility().addEffect(effect); - Target target = new TargetCreaturePermanent(); - target.setTargetTag(1); - this.getSpellAbility().addTarget(target); + "if this spell was kicked, it deals 2 damage to another target creature") + .setTargetPointer(new SecondTargetPointer())); + this.getSpellAbility().addTarget(new TargetCreaturePermanent().setTargetTag(1).withChooseHint("to return to hand")); this.getSpellAbility().setTargetAdjuster(JiltAdjuster.instance); } @@ -63,11 +59,9 @@ enum JiltAdjuster implements TargetAdjuster { if (!KickedCondition.ONCE.apply(game, ability)) { return; } - FilterCreaturePermanent filter = new FilterCreaturePermanent("Another creature: Damaged"); + FilterCreaturePermanent filter = new FilterCreaturePermanent("another target creature"); filter.add(new AnotherTargetPredicate(2)); - Target target = new TargetCreaturePermanent(filter); - target.setTargetTag(2); - ability.addTarget(target); + ability.addTarget(new TargetCreaturePermanent(filter).setTargetTag(2).withChooseHint("to deal 2 damage")); } } diff --git a/Mage.Sets/src/mage/cards/j/JohannApprenticeSorcerer.java b/Mage.Sets/src/mage/cards/j/JohannApprenticeSorcerer.java index 227cb3bcded..dbc462f6484 100644 --- a/Mage.Sets/src/mage/cards/j/JohannApprenticeSorcerer.java +++ b/Mage.Sets/src/mage/cards/j/JohannApprenticeSorcerer.java @@ -4,21 +4,20 @@ import mage.MageIdentifier; import mage.MageInt; import mage.MageObjectReference; import mage.abilities.Ability; +import mage.abilities.SpellAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.AsThoughEffectImpl; import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; -import mage.abilities.hint.Hint; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.game.Game; -import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.players.Player; -import mage.watchers.Watcher; +import mage.watchers.common.OnceEachTurnCastWatcher; -import java.util.*; +import java.util.UUID; /** * @author Susucr @@ -40,9 +39,9 @@ public final class JohannApprenticeSorcerer extends CardImpl { // Once each turn, you may cast an instant or sorcery spell from the top of your library. this.addAbility( new SimpleStaticAbility(new JohannApprenticeSorcererPlayTopEffect()) - .setIdentifier(MageIdentifier.JohannApprenticeSorcererWatcher) - .addHint(JohannApprenticeSorcererHint.instance), - new JohannApprenticeSorcererWatcher() + .setIdentifier(MageIdentifier.OnceEachTurnCastWatcher) + .addHint(OnceEachTurnCastWatcher.getHint()), + new OnceEachTurnCastWatcher() ); } @@ -56,31 +55,6 @@ public final class JohannApprenticeSorcerer extends CardImpl { } } -enum JohannApprenticeSorcererHint implements Hint { - instance; - - @Override - public String getText(Game game, Ability ability) { - JohannApprenticeSorcererWatcher watcher = game.getState().getWatcher(JohannApprenticeSorcererWatcher.class); - if (watcher != null) { - boolean used = watcher.isAbilityUsed(ability.getControllerId(), new MageObjectReference(ability.getSourceId(), game)); - if (used) { - Player player = game.getPlayer(ability.getControllerId()); - if (player != null) { - return "A spell has been cast by " + player.getLogName() + " with {this} this turn."; - } - } - } - - return ""; - } - - @Override - public JohannApprenticeSorcererHint copy() { - return this; - } -} - class JohannApprenticeSorcererPlayTopEffect extends AsThoughEffectImpl { JohannApprenticeSorcererPlayTopEffect() { @@ -104,62 +78,42 @@ class JohannApprenticeSorcererPlayTopEffect extends AsThoughEffectImpl { @Override public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { - // Only applies for the controller of the ability. - if (!affectedControllerId.equals(source.getControllerId())) { - return false; - } + throw new IllegalArgumentException("Wrong code usage: can't call applies method on empty affectedAbility"); + } + @Override + public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) { Player controller = game.getPlayer(source.getControllerId()); - JohannApprenticeSorcererWatcher watcher = game.getState().getWatcher(JohannApprenticeSorcererWatcher.class); - Permanent sourceObject = game.getPermanent(source.getSourceId()); - if (controller == null || watcher == null || sourceObject == null) { + OnceEachTurnCastWatcher watcher = game.getState().getWatcher(OnceEachTurnCastWatcher.class); + Permanent sourcePermanent = source.getSourcePermanentIfItStillExists(game); + if (controller == null || sourcePermanent == null || watcher == null) { + return false; + } + // Only applies for the controller of the ability. + if (!playerId.equals(source.getControllerId())) { return false; } - // Has the ability already been used this turn by the player? - if (watcher.isAbilityUsed(controller.getId(), new MageObjectReference(sourceObject, game))) { + if (watcher.isAbilityUsed(controller.getId(), new MageObjectReference(sourcePermanent, game))) { return false; } - Card card = game.getCard(objectId); Card topCard = controller.getLibrary().getFromTop(game); // Is the card attempted to be played the top card of the library? if (card == null || topCard == null || !topCard.getId().equals(card.getMainCard().getId())) { return false; } - - // Only works for instant & sorcery. - return card.isInstantOrSorcery(game); - } -} - -class JohannApprenticeSorcererWatcher extends Watcher { - - // player -> set of all permanent's mor that already used their once per turn Approval. - private final Map> usedFrom = new HashMap<>(); - - public JohannApprenticeSorcererWatcher() { - super(WatcherScope.GAME); - } - - @Override - public void watch(GameEvent event, Game game) { - UUID playerId = event.getPlayerId(); - if (event.getType() == GameEvent.EventType.SPELL_CAST - && event.hasApprovingIdentifier(MageIdentifier.JohannApprenticeSorcererWatcher) - && playerId != null) { - usedFrom.computeIfAbsent(playerId, k -> new HashSet<>()) - .add(event.getAdditionalReference().getApprovingMageObjectReference()); + if (affectedAbility instanceof SpellAbility) { + SpellAbility spellAbility = (SpellAbility) affectedAbility; + if (spellAbility.getManaCosts().isEmpty() + || !spellAbility.spellCanBeActivatedRegularlyNow(playerId, game)) { + return false; + } + Card cardToCheck = spellAbility.getCharacteristics(game); + // Only works for instant & sorcery. + return cardToCheck.isInstantOrSorcery(game); } + return false; } - @Override - public void reset() { - super.reset(); - usedFrom.clear(); - } - - public boolean isAbilityUsed(UUID playerId, MageObjectReference mor) { - return usedFrom.getOrDefault(playerId, Collections.emptySet()).contains(mor); - } } diff --git a/Mage.Sets/src/mage/cards/j/JolenePlunderingPugilist.java b/Mage.Sets/src/mage/cards/j/JolenePlunderingPugilist.java new file mode 100644 index 00000000000..b3b5ee926f0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/j/JolenePlunderingPugilist.java @@ -0,0 +1,71 @@ +package mage.cards.j; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksWithCreaturesTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DamageTargetEffect; +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.common.FilterControlledPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.PowerPredicate; +import mage.game.permanent.token.TreasureToken; +import mage.target.common.TargetAnyTarget; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class JolenePlunderingPugilist extends CardImpl { + + private static final FilterCreaturePermanent filter = + new FilterCreaturePermanent("creatures with power 4 or greater"); + private static final FilterControlledPermanent filterTreasure = + new FilterControlledPermanent(SubType.TREASURE, "Treasure"); + + static { + filter.add(new PowerPredicate(ComparisonType.OR_GREATER, 4)); + } + + public JolenePlunderingPugilist(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.MERCENARY); + this.power = new MageInt(4); + this.toughness = new MageInt(2); + + // Whenever you attack with one or more creatures with power 4 or greater, create a Treasure token. + this.addAbility(new AttacksWithCreaturesTriggeredAbility( + new CreateTokenEffect(new TreasureToken()), 1, filter + )); + + // {1}{R}, Sacrifice a Treasure: Jolene, Plundering Pugilist deals 1 damage to any target. + Ability ability = new SimpleActivatedAbility( + new DamageTargetEffect(1), + new ManaCostsImpl<>("{1}{R}") + ); + ability.addCost(new SacrificeTargetCost(filterTreasure)); + ability.addTarget(new TargetAnyTarget()); + this.addAbility(ability); + } + + private JolenePlunderingPugilist(final JolenePlunderingPugilist card) { + super(card); + } + + @Override + public JolenePlunderingPugilist copy() { + return new JolenePlunderingPugilist(this); + } +} diff --git a/Mage.Sets/src/mage/cards/j/JuvenileMistDragon.java b/Mage.Sets/src/mage/cards/j/JuvenileMistDragon.java index 222fb7207db..be08f005926 100644 --- a/Mage.Sets/src/mage/cards/j/JuvenileMistDragon.java +++ b/Mage.Sets/src/mage/cards/j/JuvenileMistDragon.java @@ -10,13 +10,8 @@ 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.permanent.ControllerIdPredicate; -import mage.game.Game; -import mage.players.Player; -import mage.target.TargetPermanent; -import mage.target.targetadjustment.TargetAdjuster; +import mage.target.common.TargetCreaturePermanent; +import mage.target.targetadjustment.EachOpponentPermanentTargetsAdjuster; import mage.target.targetpointer.EachTargetPointer; import java.util.UUID; @@ -47,7 +42,7 @@ public final class JuvenileMistDragon extends CardImpl { .setTargetPointer(new EachTargetPointer()) .setText("Each of those creatures doesn't untap during its controller's next untap step") ); - ability.setTargetAdjuster(JuvenileMistDragonAdjuster.instance); + ability.setTargetAdjuster(new EachOpponentPermanentTargetsAdjuster(new TargetCreaturePermanent(0,1))); this.addAbility(ability.withFlavorWord("Confounding Clouds")); } @@ -60,22 +55,3 @@ public final class JuvenileMistDragon extends CardImpl { return new JuvenileMistDragon(this); } } - -enum JuvenileMistDragonAdjuster implements TargetAdjuster { - instance; - - @Override - public void adjustTargets(Ability ability, Game game) { - ability.getTargets().clear(); - for (UUID opponentId : game.getOpponents(ability.getControllerId())) { - Player opponent = game.getPlayer(opponentId); - if (opponent == null) { - continue; - } - FilterPermanent filter = new FilterCreaturePermanent("creature controlled by " + opponent.getLogName()); - filter.add(new ControllerIdPredicate(opponentId)); - TargetPermanent target = new TargetPermanent(0, 1, filter, false); - ability.addTarget(target); - } - } -} diff --git a/Mage.Sets/src/mage/cards/k/KadenaSlinkingSorcerer.java b/Mage.Sets/src/mage/cards/k/KadenaSlinkingSorcerer.java index 72c80abcbf2..21cf949e8a3 100644 --- a/Mage.Sets/src/mage/cards/k/KadenaSlinkingSorcerer.java +++ b/Mage.Sets/src/mage/cards/k/KadenaSlinkingSorcerer.java @@ -4,7 +4,7 @@ import mage.MageInt; import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.DrawCardSourceControllerEffect; -import mage.abilities.effects.common.cost.MorphSpellsCostReductionControllerEffect; +import mage.abilities.effects.common.cost.FaceDownSpellsCostReductionControllerEffect; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -50,7 +50,7 @@ public final class KadenaSlinkingSorcerer extends CardImpl { // The first face-down creature spell you cast each turn costs {3} less to cast. this.addAbility(new SimpleStaticAbility( - new MorphSpellsCostReductionControllerEffect(filterFirstFaceDownSpell, 3) + new FaceDownSpellsCostReductionControllerEffect(filterFirstFaceDownSpell, 3) .setText("The first face-down creature spell you cast each turn costs {3} less to cast.") ), new KadenaSlinkingSorcererWatcher()); diff --git a/Mage.Sets/src/mage/cards/k/KaervekThePunisher.java b/Mage.Sets/src/mage/cards/k/KaervekThePunisher.java new file mode 100644 index 00000000000..0c1eeecac07 --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KaervekThePunisher.java @@ -0,0 +1,97 @@ +package mage.cards.k; + +import mage.ApprovingObject; +import mage.MageInt; +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.abilities.SpellAbility; +import mage.abilities.common.CommittedCrimeTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.predicate.mageobject.ColorPredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class KaervekThePunisher extends CardImpl { + + private static final FilterCard filter = new FilterCard("black card from your graveyard"); + + static { + filter.add(new ColorPredicate(ObjectColor.BLACK)); + } + + public KaervekThePunisher(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARLOCK); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Whenever you commit a crime, exile up to one target black card from your graveyard and copy it. You may cast the copy. If you do, you lose 2 life. + Ability ability = new CommittedCrimeTriggeredAbility(new KaervekThePunisherEffect()); + ability.addTarget(new TargetCardInYourGraveyard(0, 1, filter)); + this.addAbility(ability); + } + + private KaervekThePunisher(final KaervekThePunisher card) { + super(card); + } + + @Override + public KaervekThePunisher copy() { + return new KaervekThePunisher(this); + } +} + +class KaervekThePunisherEffect extends OneShotEffect { + + KaervekThePunisherEffect() { + super(Outcome.Benefit); + staticText = "exile up to one target black card from your graveyard and copy it. " + + "You may cast the copy. If you do, you lose 2 life"; + } + + private KaervekThePunisherEffect(final KaervekThePunisherEffect effect) { + super(effect); + } + + @Override + public KaervekThePunisherEffect copy() { + return new KaervekThePunisherEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Card card = game.getCard(source.getFirstTarget()); + if (controller == null || card == null) { + return false; + } + // exile the card + card.moveToExile(null, "", source, game); + Card copiedCard = game.copyCard(card, source, source.getControllerId()); + + game.getState().setValue("PlayFromNotOwnHandZone" + copiedCard.getId(), Boolean.TRUE); + // You may cast the copy. + SpellAbility spellAbility = controller.chooseAbilityForCast(copiedCard, game, false); + if (controller.cast(spellAbility, game, false, new ApprovingObject(source, game))) { + // If cast, you lose 2 life + controller.loseLife(2, game, source, false); + } + game.getState().setValue("PlayFromNotOwnHandZone" + copiedCard.getId(), null); + return true; + } + +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/k/KairiTheSwirlingSky.java b/Mage.Sets/src/mage/cards/k/KairiTheSwirlingSky.java index 7af12a0b7d5..94dbc702157 100644 --- a/Mage.Sets/src/mage/cards/k/KairiTheSwirlingSky.java +++ b/Mage.Sets/src/mage/cards/k/KairiTheSwirlingSky.java @@ -95,13 +95,17 @@ class KairiTheSwirlingSkyTarget extends TargetPermanent { if (permanent == null) { return false; } - return permanent.getManaValue() - + this.getTargets() - .stream() - .map(game::getPermanent) - .filter(Objects::nonNull) - .mapToInt(MageObject::getManaValue) - .sum() <= 6; + int added = 0; // We need to prevent the target to be counted twice on revalidation. + if (!this.getTargets().contains(id)) { + added = permanent.getManaValue();// fresh target, adding its MV + } + return added + + this.getTargets() + .stream() + .map(game::getPermanent) + .filter(Objects::nonNull) + .mapToInt(MageObject::getManaValue) + .sum() <= 6; } } diff --git a/Mage.Sets/src/mage/cards/k/KambalProfiteeringMayor.java b/Mage.Sets/src/mage/cards/k/KambalProfiteeringMayor.java new file mode 100644 index 00000000000..ae5d6c91e28 --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KambalProfiteeringMayor.java @@ -0,0 +1,155 @@ +package mage.cards.k; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.EntersBattlefieldOneOrMoreTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenCopyTargetEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.LoseLifeOpponentsEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeBatchEvent; +import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.Permanent; +import mage.game.permanent.PermanentToken; +import mage.players.Player; +import mage.target.targetpointer.FixedTarget; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * @author Susucr + */ +public final class KambalProfiteeringMayor extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("tokens"); + + static { + filter.add(TokenPredicate.TRUE); + } + + public KambalProfiteeringMayor(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ADVISOR); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // Whenever one or more tokens enter the battlefield under your opponents' control, for each of them, create a tapped token that's a copy of it. This ability triggers only once each turn. + this.addAbility(new KambalProfiteeringMayorTriggeredAbility()); + + // Whenever one or more tokens enter the battlefield under your control, each opponent loses 1 life and you gain 1 life. + Ability ability = new EntersBattlefieldOneOrMoreTriggeredAbility( + new LoseLifeOpponentsEffect(1), filter, TargetController.YOU + ); + ability.addEffect(new GainLifeEffect(1).concatBy("and")); + this.addAbility(ability); + } + + private KambalProfiteeringMayor(final KambalProfiteeringMayor card) { + super(card); + } + + @Override + public KambalProfiteeringMayor copy() { + return new KambalProfiteeringMayor(this); + } +} + +class KambalProfiteeringMayorTriggeredAbility extends TriggeredAbilityImpl { + + KambalProfiteeringMayorTriggeredAbility() { + super(Zone.BATTLEFIELD, null); + this.setTriggersOnceEachTurn(true); + } + + private KambalProfiteeringMayorTriggeredAbility(final KambalProfiteeringMayorTriggeredAbility effect) { + super(effect); + } + + @Override + public KambalProfiteeringMayorTriggeredAbility copy() { + return new KambalProfiteeringMayorTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ZONE_CHANGE_BATCH; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + ZoneChangeBatchEvent zEvent = (ZoneChangeBatchEvent) event; + Player controller = game.getPlayer(this.controllerId); + if (controller == null) { + return false; + } + List tokensIds = zEvent.getEvents() + .stream() + .filter(zce -> zce.getToZone() == Zone.BATTLEFIELD // keep enter the battlefield + && controller.hasOpponent(zce.getPlayerId(), game)) // & under your opponent's control + .map(ZoneChangeEvent::getTarget) + .filter(Objects::nonNull) + .filter(p -> p instanceof PermanentToken) // collect only tokens + .map(Permanent::getId) + .collect(Collectors.toList()); + if (tokensIds.isEmpty()) { + return false; + } + this.getEffects().clear(); + this.addEffect(new KambalProfiteeringMayorEffect(tokensIds)); + return true; + } + + @Override + public String getRule() { + return "Whenever one or more tokens enter the battlefield under your opponents' control, " + + "for each of them, create a tapped token that's a copy of it. " + + "This ability triggers only once each turn."; + } + +} + +class KambalProfiteeringMayorEffect extends OneShotEffect { + + private final List tokensIds; + + KambalProfiteeringMayorEffect(List tokensIds) { + super(Outcome.PutCreatureInPlay); + this.tokensIds = new ArrayList<>(tokensIds); + } + + private KambalProfiteeringMayorEffect(final KambalProfiteeringMayorEffect effect) { + super(effect); + this.tokensIds = new ArrayList<>(effect.tokensIds); + } + + @Override + public KambalProfiteeringMayorEffect copy() { + return new KambalProfiteeringMayorEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + for (UUID tokenId : tokensIds) { + new CreateTokenCopyTargetEffect(source.getControllerId(), null, false, 1, true, false) + .setTargetPointer(new FixedTarget(tokenId)) + .apply(game, source); + } + return true; + } + +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/k/KaradorGhostChieftain.java b/Mage.Sets/src/mage/cards/k/KaradorGhostChieftain.java index 7f7a8fa882d..37455d3b7ab 100644 --- a/Mage.Sets/src/mage/cards/k/KaradorGhostChieftain.java +++ b/Mage.Sets/src/mage/cards/k/KaradorGhostChieftain.java @@ -1,27 +1,21 @@ package mage.cards.k; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; -import mage.MageIdentifier; import mage.MageInt; -import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.SpellAbility; +import mage.abilities.common.CastFromGraveyardOnceEachTurnAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.AsThoughEffectImpl; import mage.abilities.effects.common.cost.CostModificationEffectImpl; -import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.filter.StaticFilters; +import mage.filter.common.FilterCreatureCard; import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; import mage.players.Player; import mage.util.CardUtil; -import mage.watchers.Watcher; + +import java.util.UUID; /** * @@ -29,6 +23,8 @@ import mage.watchers.Watcher; */ public final class KaradorGhostChieftain extends CardImpl { + private static final FilterCreatureCard filter = new FilterCreatureCard("a creature spell"); + public KaradorGhostChieftain(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{W}{B}{G}"); this.supertype.add(SuperType.LEGENDARY); @@ -42,10 +38,8 @@ public final class KaradorGhostChieftain extends CardImpl { this.addAbility(new SimpleStaticAbility(Zone.ALL, new KaradorGhostChieftainCostReductionEffect())); - // During each of your turns, you may cast one creature card from your graveyard. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, - new KaradorGhostChieftainCastFromGraveyardEffect()).setIdentifier(MageIdentifier.KaradorGhostChieftainWatcher), - new KaradorGhostChieftainWatcher()); + // Once during each of your turns, you may cast a creature spell from your graveyard. + this.addAbility(new CastFromGraveyardOnceEachTurnAbility(filter)); } private KaradorGhostChieftain(final KaradorGhostChieftain card) { @@ -94,73 +88,3 @@ class KaradorGhostChieftainCostReductionEffect extends CostModificationEffectImp return new KaradorGhostChieftainCostReductionEffect(this); } } - -class KaradorGhostChieftainCastFromGraveyardEffect extends AsThoughEffectImpl { - - KaradorGhostChieftainCastFromGraveyardEffect() { - super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.PutCreatureInPlay); - staticText = "During each of your turns, you may cast a creature spell from your graveyard"; - } - - private KaradorGhostChieftainCastFromGraveyardEffect(final KaradorGhostChieftainCastFromGraveyardEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - return true; - } - - @Override - public KaradorGhostChieftainCastFromGraveyardEffect copy() { - return new KaradorGhostChieftainCastFromGraveyardEffect(this); - } - - @Override - public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { - if (source.isControlledBy(affectedControllerId) - && Zone.GRAVEYARD.equals(game.getState().getZone(objectId))) { - Card objectCard = game.getCard(objectId); - Permanent sourceObject = game.getPermanent(source.getSourceId()); // needs to be onto the battlefield - if (objectCard != null - && sourceObject != null - && objectCard.isOwnedBy(source.getControllerId()) - && objectCard.isCreature(game) - && objectCard.getSpellAbility() != null - && objectCard.getSpellAbility().spellCanBeActivatedRegularlyNow(affectedControllerId, game)) { - KaradorGhostChieftainWatcher watcher - = game.getState().getWatcher(KaradorGhostChieftainWatcher.class); - return watcher != null - && !watcher.isAbilityUsed(new MageObjectReference(sourceObject, game)); - } - } - return false; - } -} - -class KaradorGhostChieftainWatcher extends Watcher { - - private final Set usedFrom = new HashSet<>(); - - KaradorGhostChieftainWatcher() { - super(WatcherScope.GAME); - } - - @Override - public void watch(GameEvent event, Game game) { - if (GameEvent.EventType.SPELL_CAST.equals(event.getType()) - && event.hasApprovingIdentifier(MageIdentifier.KaradorGhostChieftainWatcher)) { - usedFrom.add(event.getAdditionalReference().getApprovingMageObjectReference()); - } - } - - @Override - public void reset() { - super.reset(); - usedFrom.clear(); - } - - public boolean isAbilityUsed(MageObjectReference mor) { - return usedFrom.contains(mor); - } -} diff --git a/Mage.Sets/src/mage/cards/k/KarplusanMinotaur.java b/Mage.Sets/src/mage/cards/k/KarplusanMinotaur.java index 24752e8e63a..36ffdbc9158 100644 --- a/Mage.Sets/src/mage/cards/k/KarplusanMinotaur.java +++ b/Mage.Sets/src/mage/cards/k/KarplusanMinotaur.java @@ -97,7 +97,7 @@ class KarplusanMinotaurFlipLoseTriggeredAbility extends TriggeredAbilityImpl { public KarplusanMinotaurFlipLoseTriggeredAbility() { super(Zone.BATTLEFIELD, new DamageTargetEffect(1), false); this.addTarget(new TargetAnyTarget()); - targetAdjuster = KarplusanMinotaurAdjuster.instance; + this.setTargetAdjuster(KarplusanMinotaurAdjuster.instance); } private KarplusanMinotaurFlipLoseTriggeredAbility(final KarplusanMinotaurFlipLoseTriggeredAbility ability) { diff --git a/Mage.Sets/src/mage/cards/k/KayaSpiritsJustice.java b/Mage.Sets/src/mage/cards/k/KayaSpiritsJustice.java index 531853ca51a..ee1be9f3d26 100644 --- a/Mage.Sets/src/mage/cards/k/KayaSpiritsJustice.java +++ b/Mage.Sets/src/mage/cards/k/KayaSpiritsJustice.java @@ -120,9 +120,6 @@ class KayaSpiritsJusticeTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { ZoneChangeBatchEvent zEvent = (ZoneChangeBatchEvent) event; - if (zEvent == null) { - return false; - } Set battlefieldCards = zEvent.getEvents() .stream() diff --git a/Mage.Sets/src/mage/cards/k/KazarovSengirPureblood.java b/Mage.Sets/src/mage/cards/k/KazarovSengirPureblood.java index 46ce5ed3973..d09a18e848d 100644 --- a/Mage.Sets/src/mage/cards/k/KazarovSengirPureblood.java +++ b/Mage.Sets/src/mage/cards/k/KazarovSengirPureblood.java @@ -75,7 +75,7 @@ class KazarovSengirPurebloodTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT; } @Override diff --git a/Mage.Sets/src/mage/cards/k/KellanJoinsUp.java b/Mage.Sets/src/mage/cards/k/KellanJoinsUp.java new file mode 100644 index 00000000000..c8aae837b0e --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KellanJoinsUp.java @@ -0,0 +1,60 @@ +package mage.cards.k; + +import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.MayExileCardFromHandPlottedEffect; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.FilterCard; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.ManaValuePredicate; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class KellanJoinsUp extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("legendary creature"); + private static final FilterCard filterCard = new FilterCard("nonland card with mana value 3 or less"); + + static { + filter.add(SuperType.LEGENDARY.getPredicate()); + filterCard.add(Predicates.not(CardType.LAND.getPredicate())); + filterCard.add(new ManaValuePredicate(ComparisonType.OR_LESS, 3)); + } + + public KellanJoinsUp(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{G}{W}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + + // When Kellan Joins Up enters the battlefield, you may exile a nonland card with mana value 3 or less from your hand. If you do, it becomes plotted. + this.addAbility(new EntersBattlefieldTriggeredAbility(new MayExileCardFromHandPlottedEffect(filterCard))); + + // Whenever a legendary creature enters the battlefield under your control, put a +1/+1 counter on each creature you control. + this.addAbility(new EntersBattlefieldControlledTriggeredAbility( + new AddCountersAllEffect( + CounterType.P1P1.createInstance(), + StaticFilters.FILTER_CONTROLLED_CREATURE + ), filter + )); + } + + private KellanJoinsUp(final KellanJoinsUp card) { + super(card); + } + + @Override + public KellanJoinsUp copy() { + return new KellanJoinsUp(this); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/k/KellanTheKid.java b/Mage.Sets/src/mage/cards/k/KellanTheKid.java new file mode 100644 index 00000000000..29f91651fe2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KellanTheKid.java @@ -0,0 +1,98 @@ +package mage.cards.k; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.PutCardFromHandOntoBattlefieldEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.LifelinkAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.CardsImpl; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.FilterSpell; +import mage.filter.StaticFilters; +import mage.filter.common.FilterPermanentCard; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.card.CastFromZonePredicate; +import mage.filter.predicate.mageobject.ManaValuePredicate; +import mage.game.Game; +import mage.game.stack.Spell; +import mage.players.Player; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class KellanTheKid extends CardImpl { + + private static final FilterSpell filter = new FilterSpell("a spell from anywhere other than your hand"); + + static { + filter.add(Predicates.not(new CastFromZonePredicate(Zone.HAND))); + } + + public KellanTheKid(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.FAERIE); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // Whenever you cast a spell from anywhere other than your hand, you may cast a permanent spell with equal or lesser mana value from your hand without paying its mana cost. If you don't, you may put a land card from your hand onto the battlefield. + this.addAbility(new SpellCastControllerTriggeredAbility(new KellanTheKidEffect(), filter, false)); + } + + private KellanTheKid(final KellanTheKid card) { + super(card); + } + + @Override + public KellanTheKid copy() { + return new KellanTheKid(this); + } +} + +class KellanTheKidEffect extends OneShotEffect { + + KellanTheKidEffect() { + super(Outcome.Benefit); + staticText = "you may cast a permanent spell with equal or lesser mana value from your hand without " + + "paying its mana cost. If you don't, you may put a land card from your hand onto the battlefield"; + } + + private KellanTheKidEffect(final KellanTheKidEffect effect) { + super(effect); + } + + @Override + public KellanTheKidEffect copy() { + return new KellanTheKidEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Spell spell = (Spell) getValue("spellCast"); + if (player == null || spell == null) { + return false; + } + FilterCard filter = new FilterPermanentCard(); + filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, spell.getManaValue() + 1)); + return CardUtil.castSpellWithAttributesForFree(player, source, game, new CardsImpl(player.getHand()), filter) + || new PutCardFromHandOntoBattlefieldEffect(StaticFilters.FILTER_CARD_LAND).apply(game, source); + } +} diff --git a/Mage.Sets/src/mage/cards/k/KirriTalentedSprout.java b/Mage.Sets/src/mage/cards/k/KirriTalentedSprout.java new file mode 100644 index 00000000000..5b20e423fbf --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KirriTalentedSprout.java @@ -0,0 +1,69 @@ +package mage.cards.k; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfPostCombatMainTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +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.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class KirriTalentedSprout extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("Plants and Treefolk"); + private static final FilterCard filter2 = new FilterCard("Plant, Treefolk, or land card from your graveyard"); + + static { + filter.add(Predicates.or( + SubType.PLANT.getPredicate(), + SubType.TREEFOLK.getPredicate() + )); + filter2.add(Predicates.or( + SubType.PLANT.getPredicate(), + SubType.TREEFOLK.getPredicate(), + CardType.LAND.getPredicate() + )); + } + + public KirriTalentedSprout(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}{G}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.PLANT); + this.subtype.add(SubType.DRUID); + this.power = new MageInt(0); + this.toughness = new MageInt(3); + + // Other Plants and Treefolk you control get +2/+0. + this.addAbility(new SimpleStaticAbility(new BoostControlledEffect( + 2, 0, Duration.WhileControlled, filter, true + ))); + + // At the beginning of your postcombat main phase, return target Plant, Treefolk, or land card from your graveyard to your hand. + Ability ability = new BeginningOfPostCombatMainTriggeredAbility( + new ReturnFromGraveyardToHandTargetEffect(), TargetController.YOU, false + ); + ability.addTarget(new TargetCardInYourGraveyard(filter2)); + this.addAbility(ability); + } + + private KirriTalentedSprout(final KirriTalentedSprout card) { + super(card); + } + + @Override + public KirriTalentedSprout copy() { + return new KirriTalentedSprout(this); + } +} diff --git a/Mage.Sets/src/mage/cards/k/KorlessaScaleSinger.java b/Mage.Sets/src/mage/cards/k/KorlessaScaleSinger.java index a400536ed69..da44063bc9e 100644 --- a/Mage.Sets/src/mage/cards/k/KorlessaScaleSinger.java +++ b/Mage.Sets/src/mage/cards/k/KorlessaScaleSinger.java @@ -3,7 +3,7 @@ package mage.cards.k; import mage.MageInt; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; -import mage.abilities.effects.common.continuous.PlayTheTopCardEffect; +import mage.abilities.effects.common.continuous.PlayFromTopOfLibraryEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -38,7 +38,7 @@ public final class KorlessaScaleSinger extends CardImpl { this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); // You may cast Dragon spells from the top of your library. - this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(TargetController.YOU, filter, false))); + this.addAbility(new SimpleStaticAbility(new PlayFromTopOfLibraryEffect(filter))); } private KorlessaScaleSinger(final KorlessaScaleSinger card) { diff --git a/Mage.Sets/src/mage/cards/k/KraumViolentCacophony.java b/Mage.Sets/src/mage/cards/k/KraumViolentCacophony.java new file mode 100644 index 00000000000..96b5b39783d --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KraumViolentCacophony.java @@ -0,0 +1,49 @@ +package mage.cards.k; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.CastSecondSpellTriggeredAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class KraumViolentCacophony extends CardImpl { + + public KraumViolentCacophony(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.ZOMBIE); + this.subtype.add(SubType.HORROR); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever you cast your second spell each turn, put a +1/+1 counter on Kraum, Violent Cacophony and draw a card. + Ability ability = new CastSecondSpellTriggeredAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance())); + ability.addEffect(new DrawCardSourceControllerEffect(1).concatBy("and")); + this.addAbility(ability); + } + + private KraumViolentCacophony(final KraumViolentCacophony card) { + super(card); + } + + @Override + public KraumViolentCacophony copy() { + return new KraumViolentCacophony(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LaeliaTheBladeReforged.java b/Mage.Sets/src/mage/cards/l/LaeliaTheBladeReforged.java index 64a44989707..5bbad75c84c 100644 --- a/Mage.Sets/src/mage/cards/l/LaeliaTheBladeReforged.java +++ b/Mage.Sets/src/mage/cards/l/LaeliaTheBladeReforged.java @@ -13,12 +13,18 @@ import mage.constants.*; import mage.counters.CounterType; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.events.ZoneChangeGroupEvent; +import mage.game.events.ZoneChangeBatchEvent; +import mage.game.events.ZoneChangeEvent; import java.util.Objects; import java.util.UUID; /** + * Rules update: 6/18/2021 + * Laelia, the Blade Reforged has received an update to its Oracle text. + * Specifically, its last triggered ability doesn't care which player is exiling cards from the library or graveyard. + * Cards put into exile from your library or graveyard for any reason, such as the delve ability, cause the ability to trigger. + * * @author jmharmon */ public final class LaeliaTheBladeReforged extends CardImpl { @@ -38,7 +44,7 @@ public final class LaeliaTheBladeReforged extends CardImpl { // Whenever Laelia, the Blade Reforged attacks, exile the top card of your library. You may play that card this turn. this.addAbility(new AttacksTriggeredAbility(new ExileTopXMayPlayUntilEffect(1, Duration.EndOfTurn), false)); - // Whenever a spell or ability you control exiles one or more cards from your library and/or your graveyard, put a +1/+1 counter on Laelia. + // Whenever one or more cards are put into exile from your library and/or your graveyard, put a +1/+1 counter on Laelia. this.addAbility(new LaeliaTheBladeReforgedAddCountersTriggeredAbility()); } @@ -69,44 +75,21 @@ class LaeliaTheBladeReforgedAddCountersTriggeredAbility extends TriggeredAbility @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.ZONE_CHANGE_GROUP; + return event.getType() == GameEvent.EventType.ZONE_CHANGE_BATCH; } @Override public boolean checkTrigger(GameEvent event, Game game) { - ZoneChangeGroupEvent zEvent = (ZoneChangeGroupEvent) event; - final int numberExiled = zEvent.getCards().size(); - if (zEvent.getToZone() != Zone.EXILED - || numberExiled == 0) { - return false; - } - switch (zEvent.getFromZone()) { - case LIBRARY: - if (zEvent - .getCards() - .stream() - .filter(Objects::nonNull) - .map(Card::getOwnerId) - .anyMatch(this::isControlledBy) - && numberExiled > 0) { - this.getEffects().clear(); - this.getEffects().add(new AddCountersSourceEffect(CounterType.P1P1.createInstance())); - return true; - } - case GRAVEYARD: - if (zEvent - .getCards() - .stream() - .filter(Objects::nonNull) - .map(Card::getOwnerId) - .anyMatch(this::isControlledBy) - && numberExiled > 0) { - this.getEffects().clear(); - this.getEffects().add(new AddCountersSourceEffect(CounterType.P1P1.createInstance())); - return true; - } - } - return false; + ZoneChangeBatchEvent zEvent = (ZoneChangeBatchEvent) event; + return zEvent.getEvents() + .stream() + .filter(e -> e.getFromZone() == Zone.LIBRARY || e.getFromZone() == Zone.GRAVEYARD) + .filter(e -> e.getToZone() == Zone.EXILED) + .map(ZoneChangeEvent::getTargetId) + .map(game::getCard) + .filter(Objects::nonNull) + .map(Card::getOwnerId) + .anyMatch(this::isControlledBy); } @Override diff --git a/Mage.Sets/src/mage/cards/l/LassoedByTheLaw.java b/Mage.Sets/src/mage/cards/l/LassoedByTheLaw.java new file mode 100644 index 00000000000..e661841406c --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LassoedByTheLaw.java @@ -0,0 +1,41 @@ +package mage.cards.l; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.ExileUntilSourceLeavesEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; +import mage.game.permanent.token.MercenaryToken; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LassoedByTheLaw extends CardImpl { + + public LassoedByTheLaw(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{W}"); + + // When Lassoed by the Law enters the battlefield, exile target nonland permanent an opponent controls until Lassoed by the Law leaves the battlefield. + Ability ability = new EntersBattlefieldTriggeredAbility(new ExileUntilSourceLeavesEffect()); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_OPPONENTS_PERMANENT_NON_LAND)); + this.addAbility(ability); + + // When Lassoed by the Law enters the battlefield, create a 1/1 red Mercenary creature token with "{T}: Target creature you control gets +1/+0 until end of turn. Activate only as a sorcery." + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new MercenaryToken()))); + } + + private LassoedByTheLaw(final LassoedByTheLaw card) { + super(card); + } + + @Override + public LassoedByTheLaw copy() { + return new LassoedByTheLaw(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LaughingJasperFlint.java b/Mage.Sets/src/mage/cards/l/LaughingJasperFlint.java new file mode 100644 index 00000000000..b80e5615d55 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LaughingJasperFlint.java @@ -0,0 +1,119 @@ +package mage.cards.l; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.AddCardSubtypeAllEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.cards.*; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.mageobject.OutlawPredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetOpponent; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LaughingJasperFlint extends CardImpl { + + private static final FilterPermanent filter + = new FilterControlledCreaturePermanent("creatures you control but don't own"); + + static { + filter.add(TargetController.NOT_YOU.getOwnerPredicate()); + } + + public LaughingJasperFlint(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.LIZARD); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // Creatures you control but don't own are Mercenaries in addition to their other types. + this.addAbility(new SimpleStaticAbility(new AddCardSubtypeAllEffect( + filter, SubType.MERCENARY, DependencyType.AddingCreatureType + ))); + + // At the beginning of your upkeep, exile the top X cards of target opponent's library, where X is the number of outlaws you control. Until end of turn, you may cast spells from among those cards, and mana of any type can be spent to cast those spells. + Ability ability = new BeginningOfUpkeepTriggeredAbility( + new LaughingJasperFlintEffect(), TargetController.YOU, false + ); + ability.addTarget(new TargetOpponent()); + this.addAbility(ability.addHint(LaughingJasperFlintEffect.getHint())); + } + + private LaughingJasperFlint(final LaughingJasperFlint card) { + super(card); + } + + @Override + public LaughingJasperFlint copy() { + return new LaughingJasperFlint(this); + } +} + +class LaughingJasperFlintEffect extends OneShotEffect { + + private static final FilterPermanent filter = new FilterControlledPermanent(); + + static { + filter.add(OutlawPredicate.instance); + } + + private static final Hint hint = new ValueHint( + "Outlaws you control", new PermanentsOnBattlefieldCount(filter) + ); + + public static Hint getHint() { + return hint; + } + + LaughingJasperFlintEffect() { + super(Outcome.Benefit); + staticText = "exile the top X cards of target opponent's library, " + + "where X is the number of outlaws you control. Until end of turn, " + + "you may cast spells from among those cards, and mana of any type can be spent to cast those spells"; + } + + private LaughingJasperFlintEffect(final LaughingJasperFlintEffect effect) { + super(effect); + } + + @Override + public LaughingJasperFlintEffect copy() { + return new LaughingJasperFlintEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); + if (controller == null || opponent == null) { + return false; + } + int count = game.getBattlefield().count(filter, source.getControllerId(), source, game); + Cards cards = new CardsImpl(opponent.getLibrary().getTopCards(game, count)); + if (cards.isEmpty()) { + return false; + } + controller.moveCards(cards, Zone.EXILED, source, game); + for (Card card : cards.getCards(game)) { + CardUtil.makeCardPlayable(game, source, card, Duration.EndOfTurn, true); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/l/LavaspurBoots.java b/Mage.Sets/src/mage/cards/l/LavaspurBoots.java new file mode 100644 index 00000000000..ba6cf549d56 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LavaspurBoots.java @@ -0,0 +1,52 @@ +package mage.cards.l; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.mana.GenericManaCost; +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.WardAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AttachmentType; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LavaspurBoots extends CardImpl { + + public LavaspurBoots(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}"); + + this.subtype.add(SubType.EQUIPMENT); + + // Equipped creature gets +1/+0 and has haste and ward {1}. + Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(1, 0)); + ability.addEffect(new GainAbilityAttachedEffect( + HasteAbility.getInstance(), AttachmentType.EQUIPMENT, Duration.WhileOnBattlefield + ).setText("and has haste")); + ability.addEffect(new GainAbilityAttachedEffect( + new WardAbility(new GenericManaCost(1)), AttachmentType.EQUIPMENT, Duration.WhileOnBattlefield + ).setText("and ward {1}")); + this.addAbility(ability); + + // Equip {1} + this.addAbility(new EquipAbility(1, false)); + } + + private LavaspurBoots(final LavaspurBoots card) { + super(card); + } + + @Override + public LavaspurBoots copy() { + return new LavaspurBoots(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LazavFamiliarStranger.java b/Mage.Sets/src/mage/cards/l/LazavFamiliarStranger.java new file mode 100644 index 00000000000..08b12088fb4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LazavFamiliarStranger.java @@ -0,0 +1,107 @@ +package mage.cards.l; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.CommittedCrimeTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +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.game.permanent.Permanent; +import mage.game.permanent.PermanentCard; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInGraveyard; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class LazavFamiliarStranger extends CardImpl { + + public LazavFamiliarStranger(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SHAPESHIFTER); + this.power = new MageInt(1); + this.toughness = new MageInt(4); + + // Whenever you commit a crime, put a +1/+1 counter on Lazav, Familiar Stranger. Then you may exile a card from a graveyard. If a creature card was exiled this way, you may have Lazav become a copy of that card until end of turn. This ability triggers only once each turn. + Ability ability = new CommittedCrimeTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()) + ).setTriggersOnceEachTurn(true); + ability.addEffect(new LazavFamiliarStrangerEffect()); + this.addAbility(ability); + } + + private LazavFamiliarStranger(final LazavFamiliarStranger card) { + super(card); + } + + @Override + public LazavFamiliarStranger copy() { + return new LazavFamiliarStranger(this); + } +} + +class LazavFamiliarStrangerEffect extends OneShotEffect { + + LazavFamiliarStrangerEffect() { + super(Outcome.PutCreatureInPlay); + staticText = "Then you may exile a card from a graveyard. " + + "If a creature card was exiled this way, " + + "you may have {this} become a copy of that card until end of turn."; + } + + private LazavFamiliarStrangerEffect(final LazavFamiliarStrangerEffect effect) { + super(effect); + } + + @Override + public LazavFamiliarStrangerEffect copy() { + return new LazavFamiliarStrangerEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + TargetCard target = new TargetCardInGraveyard(0, 1); + target.withNotTarget(true); + player.choose(outcome, target, source, game); + Card card = game.getCard(target.getFirstTarget()); + if (card == null) { + return false; + } + boolean flag = card.isCreature(game); + player.moveCards(card, Zone.EXILED, source, game); + if (!flag) { + return true; + } + Permanent lazav = source.getSourcePermanentIfItStillExists(game); + if (lazav == null) { + return true; + } + if (player.chooseUse( + Outcome.PutCreatureInPlay, + "Do you want " + lazav.getLogName() + " to become a copy of " + card.getLogName() + " until end of turn?", + source, game + )) { + game.copyPermanent( + Duration.EndOfTurn, + new PermanentCard(card, source.getControllerId(), game), + source.getSourceId(), source, null + ); + } + return true; + } + +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/l/LegateLaniusCaesarsAce.java b/Mage.Sets/src/mage/cards/l/LegateLaniusCaesarsAce.java new file mode 100644 index 00000000000..e8ed7b3ba97 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LegateLaniusCaesarsAce.java @@ -0,0 +1,102 @@ +package mage.cards.l; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SacrificePermanentTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.SacrificeOpponentsEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +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.players.Player; +import mage.target.common.TargetSacrifice; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** + * @author notgreat + */ +public final class LegateLaniusCaesarsAce extends CardImpl { + + public LegateLaniusCaesarsAce(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.SOLDIER); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Decimate -- When Legate Lanius enters the battlefield, each opponent sacrifices a tenth of the creatures they control, rounded up. + this.addAbility(new EntersBattlefieldTriggeredAbility(new LegateLaniusCaesarsAceSacrificeEffect()).withFlavorWord("Decimate")); + + // Whenever an opponent sacrifices a creature, put a +1/+1 counter on Legate Lanius. + this.addAbility(new SacrificePermanentTriggeredAbility(Zone.BATTLEFIELD, + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), + StaticFilters.FILTER_PERMANENT_A_CREATURE, TargetController.OPPONENT, SetTargetPointer.NONE, false + )); + } + + private LegateLaniusCaesarsAce(final LegateLaniusCaesarsAce card) { + super(card); + } + + @Override + public LegateLaniusCaesarsAce copy() { + return new LegateLaniusCaesarsAce(this); + } +} + +//Based on SacrificeAllEffect +class LegateLaniusCaesarsAceSacrificeEffect extends OneShotEffect { + LegateLaniusCaesarsAceSacrificeEffect() { + super(Outcome.Sacrifice); + this.staticText = "each opponent sacrifices a tenth of the creatures they control, rounded up."; + } + + private LegateLaniusCaesarsAceSacrificeEffect(final LegateLaniusCaesarsAceSacrificeEffect effect) { + super(effect); + } + + @Override + public LegateLaniusCaesarsAceSacrificeEffect copy() { + return new LegateLaniusCaesarsAceSacrificeEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Set perms = new HashSet<>(); + for (UUID playerId : game.getOpponents(source.getControllerId())) { + Player player = game.getPlayer(playerId); + if (player == null) { + continue; + } + // 1/10 rounded up + int num = (game.getBattlefield().count(StaticFilters.FILTER_PERMANENT_A_CREATURE,playerId,source, game)+9)/10; + int numTargets = Math.min(num, game.getBattlefield().count(TargetSacrifice.makeFilter(StaticFilters.FILTER_PERMANENT_A_CREATURE), player.getId(), source, game)); + if (numTargets < 1) { + continue; + } + TargetSacrifice target = new TargetSacrifice(numTargets, StaticFilters.FILTER_PERMANENT_A_CREATURE); + while (!target.isChosen() && target.canChoose(playerId, source, game) && player.canRespond()) { + player.choose(Outcome.Sacrifice, target, source, game); + } + perms.addAll(target.getTargets()); + } + for (UUID permID : perms) { + Permanent permanent = game.getPermanent(permID); + if (permanent != null) { + permanent.sacrifice(source, game); + } + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/l/LegionExtruder.java b/Mage.Sets/src/mage/cards/l/LegionExtruder.java new file mode 100644 index 00000000000..4e61a3ffe12 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LegionExtruder.java @@ -0,0 +1,48 @@ +package mage.cards.l; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; +import mage.game.permanent.token.GolemToken; +import mage.target.common.TargetAnyTarget; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class LegionExtruder extends CardImpl { + + public LegionExtruder(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{R}"); + + // When Legion Foundry enters the battlefield, it deals 2 damage to any target. + Ability ability = new EntersBattlefieldTriggeredAbility(new DamageTargetEffect(2, "it"), false); + ability.addTarget(new TargetAnyTarget()); + this.addAbility(ability); + + // {2}, {T}, Sacrifice another artifact: Create a 3/3 colorless Golem artifact creature token. + ability = new SimpleActivatedAbility(new CreateTokenEffect(new GolemToken()), new GenericManaCost(2)); + ability.addCost(new TapSourceCost()); + ability.addCost(new SacrificeTargetCost(StaticFilters.FILTER_CONTROLLED_ANOTHER_ARTIFACT_SHORT_TEXT)); + this.addAbility(ability); + } + + private LegionExtruder(final LegionExtruder card) { + super(card); + } + + @Override + public LegionExtruder copy() { + return new LegionExtruder(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LeylineDowser.java b/Mage.Sets/src/mage/cards/l/LeylineDowser.java new file mode 100644 index 00000000000..6421d02cb21 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LeylineDowser.java @@ -0,0 +1,58 @@ +package mage.cards.l; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.common.TapTargetCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.MillThenPutInHandEffect; +import mage.abilities.effects.common.UntapSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SuperType; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.permanent.TappedPredicate; +import mage.target.common.TargetControlledPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LeylineDowser extends CardImpl { + + private static final FilterControlledPermanent filter = new FilterControlledCreaturePermanent("an untapped legendary creature you control"); + + static { + filter.add(TappedPredicate.UNTAPPED); + filter.add(SuperType.LEGENDARY.getPredicate()); + } + + public LeylineDowser(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); + + // {1}, {T}: Mill a card. You may put an instant or sorcery card milled this way into your hand. + Ability ability = new SimpleActivatedAbility( + new MillThenPutInHandEffect(1, StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY), new GenericManaCost(1) + ); + ability.addCost(new TapSourceCost()); + this.addAbility(ability); + + // Tap an untapped legendary creature you control: Untap Leyline Dowser. + this.addAbility(new SimpleActivatedAbility( + new UntapSourceEffect(), new TapTargetCost(new TargetControlledPermanent(filter)) + )); + } + + private LeylineDowser(final LeylineDowser card) { + super(card); + } + + @Override + public LeylineDowser copy() { + return new LeylineDowser(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LeylineOfCombustion.java b/Mage.Sets/src/mage/cards/l/LeylineOfCombustion.java index cc442623c37..88812c36eb7 100644 --- a/Mage.Sets/src/mage/cards/l/LeylineOfCombustion.java +++ b/Mage.Sets/src/mage/cards/l/LeylineOfCombustion.java @@ -83,14 +83,14 @@ class LeylineOfCombustionTriggeredAbility extends TriggeredAbilityImpl { // If a spell targets you and/or a permanent you control multiple times, // or if a spell targets you and one or more permanents you control, // Leyline of Combustion’s triggered ability triggers once. - Set sourceObjects = (Set) game.getState().getValue("sourceObjects" + this.id); + Set sourceObjects = (Set) game.getState().getValue("sourceObjects" + this.getId()); if (sourceObjects == null) { sourceObjects = new HashSet<>(); } if (!sourceObjects.add(sourceObject.getId())) { return false; } - game.getState().setValue("sourceObjects" + this.id, sourceObjects); + game.getState().setValue("sourceObjects" + this.getId(), sourceObjects); this.getEffects().clear(); Effect effect = new DamageTargetEffect(2); effect.setTargetPointer(new FixedTarget(event.getPlayerId(), game)); diff --git a/Mage.Sets/src/mage/cards/l/Lich.java b/Mage.Sets/src/mage/cards/l/Lich.java index 8c0068ca351..4be19bb51ee 100644 --- a/Mage.Sets/src/mage/cards/l/Lich.java +++ b/Mage.Sets/src/mage/cards/l/Lich.java @@ -118,7 +118,7 @@ class LichDamageTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PLAYER; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PLAYER; } @Override diff --git a/Mage.Sets/src/mage/cards/l/LilaHospitalityHostess.java b/Mage.Sets/src/mage/cards/l/LilaHospitalityHostess.java index fc945da042c..dd5703ff43e 100644 --- a/Mage.Sets/src/mage/cards/l/LilaHospitalityHostess.java +++ b/Mage.Sets/src/mage/cards/l/LilaHospitalityHostess.java @@ -4,7 +4,7 @@ import mage.MageInt; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.continuous.BoostControlledEffect; import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; -import mage.abilities.effects.common.continuous.PlayTheTopCardEffect; +import mage.abilities.effects.common.continuous.PlayFromTopOfLibraryEffect; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -41,9 +41,7 @@ public final class LilaHospitalityHostess extends CardImpl { this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); // You may cast common spells from the top of your library. - this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect( - TargetController.YOU, filter, false - ))); + this.addAbility(new SimpleStaticAbility(new PlayFromTopOfLibraryEffect(filter))); // Guests you control get +1/+1. this.addAbility(new SimpleStaticAbility(new BoostControlledEffect( diff --git a/Mage.Sets/src/mage/cards/l/LilahUndefeatedSlickshot.java b/Mage.Sets/src/mage/cards/l/LilahUndefeatedSlickshot.java new file mode 100644 index 00000000000..5f17d65ae2d --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LilahUndefeatedSlickshot.java @@ -0,0 +1,152 @@ +package mage.cards.l; + +import mage.MageInt; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.keyword.PlotAbility; +import mage.abilities.keyword.ProwessAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterSpell; +import mage.filter.common.FilterInstantOrSorcerySpell; +import mage.filter.predicate.mageobject.MulticoloredPredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.game.stack.Spell; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class LilahUndefeatedSlickshot extends CardImpl { + + public LilahUndefeatedSlickshot(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.ROGUE); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Prowess + this.addAbility(new ProwessAbility()); + + // Whenever you cast a multicolored instant or sorcery spell from your hand, exile that spell instead of putting it into your graveyard as it resolves. If you do, it becomes plotted. + this.addAbility(new LilahUndefeatedSlickshotTriggeredAbility()); + } + + private LilahUndefeatedSlickshot(final LilahUndefeatedSlickshot card) { + super(card); + } + + @Override + public LilahUndefeatedSlickshot copy() { + return new LilahUndefeatedSlickshot(this); + } +} + +class LilahUndefeatedSlickshotTriggeredAbility extends TriggeredAbilityImpl { + + private static final FilterSpell filter = new FilterInstantOrSorcerySpell(); + + static { + filter.add(MulticoloredPredicate.instance); + } + + LilahUndefeatedSlickshotTriggeredAbility() { + super(Zone.BATTLEFIELD, null, false); + } + + private LilahUndefeatedSlickshotTriggeredAbility(final LilahUndefeatedSlickshotTriggeredAbility ability) { + super(ability); + } + + @Override + public LilahUndefeatedSlickshotTriggeredAbility copy() { + return new LilahUndefeatedSlickshotTriggeredAbility(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.getStack().getSpell(event.getTargetId()); + if (spell == null || !filter.match(spell, getControllerId(), this, game) || !Zone.HAND.equals(spell.getFromZone())) { + return false; + } + this.getEffects().clear(); + this.addEffect(new LilahUndefeatedSlickshotEffect(new MageObjectReference(spell, game))); + return true; + } + + @Override + public String getRule() { + return "Whenever you cast a multicolored instant or sorcery spell from your hand, " + + "exile that spell instead of putting it into your graveyard as it resolves. " + + "If you do, it becomes plotted."; + } +} + +/** + * Quite inspired by {@link mage.cards.f.FeatherTheRedeemed Feather the Redeemed} + */ +class LilahUndefeatedSlickshotEffect extends ReplacementEffectImpl { + + private final MageObjectReference mor; + + LilahUndefeatedSlickshotEffect(MageObjectReference mor) { + super(Duration.WhileOnStack, Outcome.Benefit); + this.mor = mor; + } + + private LilahUndefeatedSlickshotEffect(final LilahUndefeatedSlickshotEffect effect) { + super(effect); + this.mor = effect.mor; + } + + @Override + public LilahUndefeatedSlickshotEffect copy() { + return new LilahUndefeatedSlickshotEffect(this); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Spell sourceSpell = game.getStack().getSpell(event.getTargetId()); + if (sourceSpell == null || sourceSpell.isCopy()) { + return false; + } + PlotAbility.doExileAndPlotCard(sourceSpell, game, source); + return true; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ZONE_CHANGE; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + ZoneChangeEvent zEvent = ((ZoneChangeEvent) event); + if (zEvent.getFromZone() != Zone.STACK + || zEvent.getToZone() != Zone.GRAVEYARD + || event.getSourceId() == null + || !event.getSourceId().equals(event.getTargetId()) + || mor.getZoneChangeCounter() != game.getState().getZoneChangeCounter(event.getSourceId())) { + return false; + } + Spell spell = game.getStack().getSpell(mor.getSourceId()); + return spell != null && spell.isInstantOrSorcery(game); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/l/LilyBowenRagingGrandma.java b/Mage.Sets/src/mage/cards/l/LilyBowenRagingGrandma.java new file mode 100644 index 00000000000..0441dfde7e9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LilyBowenRagingGrandma.java @@ -0,0 +1,111 @@ +package mage.cards.l; + +import java.util.UUID; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.condition.common.SourceMatchesFilterCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DoubleCountersSourceEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.constants.*; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.counters.CounterType; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.PowerPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; + +/** + * @author Cguy7777 + */ +public final class LilyBowenRagingGrandma extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(); + + static { + filter.add(new PowerPredicate(ComparisonType.OR_LESS, 16)); + } + + public LilyBowenRagingGrandma(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.MUTANT); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(0); + this.toughness = new MageInt(0); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Lily Bowen, Raging Grandma enters the battlefield with two +1/+1 counters on it. + this.addAbility(new EntersBattlefieldAbility( + new AddCountersSourceEffect( + CounterType.P1P1.createInstance(2)), + "with two +1/+1 counters on it")); + + // At the beginning of your upkeep, double the number of +1/+1 counters on Lily Bowen if its power is 16 or less. + // Otherwise, remove all but one +1/+1 counter from it, then you gain 1 life for each +1/+1 counter removed this way. + this.addAbility(new BeginningOfUpkeepTriggeredAbility( + new ConditionalOneShotEffect( + new DoubleCountersSourceEffect(CounterType.P1P1), + new LilyBowenRagingGrandmaEffect(), + new SourceMatchesFilterCondition(filter), + "double the number of +1/+1 counters on {this} if its power is 16 or less. " + + "Otherwise, remove all but one +1/+1 counter from it, " + + "then you gain 1 life for each +1/+1 counter removed this way"), + TargetController.YOU, + false)); + } + + private LilyBowenRagingGrandma(final LilyBowenRagingGrandma card) { + super(card); + } + + @Override + public LilyBowenRagingGrandma copy() { + return new LilyBowenRagingGrandma(this); + } +} + +class LilyBowenRagingGrandmaEffect extends OneShotEffect { + + LilyBowenRagingGrandmaEffect() { + super(Outcome.Benefit); + } + + private LilyBowenRagingGrandmaEffect(final LilyBowenRagingGrandmaEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + if (permanent == null) { + return false; + } + + // Remove all but one +1/+1 counter from it, then you gain 1 life for each +1/+1 counter removed this way. + int count = permanent.getCounters(game).getCount(CounterType.P1P1); + if (count <= 1) { + return true; + } + + int countToRemove = count - 1; + permanent.removeCounters(CounterType.P1P1.createInstance(countToRemove), source, game); + new GainLifeEffect(countToRemove).apply(game, source); + return true; + } + + @Override + public LilyBowenRagingGrandmaEffect copy() { + return new LilyBowenRagingGrandmaEffect(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LivelyDirge.java b/Mage.Sets/src/mage/cards/l/LivelyDirge.java new file mode 100644 index 00000000000..dd73357b2e5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LivelyDirge.java @@ -0,0 +1,117 @@ +package mage.cards.l; + +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.search.SearchLibraryPutInGraveyardEffect; +import mage.abilities.keyword.SpreeAbility; +import mage.cards.*; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.filter.common.FilterCreatureCard; +import mage.filter.predicate.mageobject.ManaValuePredicate; +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 LivelyDirge extends CardImpl { + + public LivelyDirge(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{B}"); + + // Spree + this.addAbility(new SpreeAbility(this)); + + // + {1} -- Search your library for a card, put it into your graveyard, then shuffle. + this.getSpellAbility().addEffect(new SearchLibraryPutInGraveyardEffect(false)); + this.getSpellAbility().withFirstModeCost(new GenericManaCost(1)); + + // + {2} -- Return up to two creature cards with total mana value 4 or less from your graveyard to the battlefield. + this.getSpellAbility().addMode(new Mode(new LivelyDirgeEffect()).withCost(new GenericManaCost(2))); + } + + private LivelyDirge(final LivelyDirge card) { + super(card); + } + + @Override + public LivelyDirge copy() { + return new LivelyDirge(this); + } +} + +class LivelyDirgeEffect extends OneShotEffect { + + LivelyDirgeEffect() { + super(Outcome.Benefit); + staticText = "return up to two creature cards with total mana value 4 or less from your graveyard to the battlefield"; + } + + private LivelyDirgeEffect(final LivelyDirgeEffect effect) { + super(effect); + } + + @Override + public LivelyDirgeEffect copy() { + return new LivelyDirgeEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + TargetCard targetCard = new LivelyDirgeTarget(); + player.choose(outcome, targetCard, source, game); + Cards cards = new CardsImpl(targetCard.getTargets()); + return player.moveCards(cards, Zone.BATTLEFIELD, source, game); + } +} + +class LivelyDirgeTarget extends TargetCardInYourGraveyard { + + private static final FilterCard filter + = new FilterCreatureCard("creature cards with total mana value 4 or less from your graveyard"); + + static { + filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, 5)); + } + + LivelyDirgeTarget() { + super(0, 2, filter, true); + } + + private LivelyDirgeTarget(final LivelyDirgeTarget target) { + super(target); + } + + @Override + public LivelyDirgeTarget copy() { + return new LivelyDirgeTarget(this); + } + + @Override + public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { + if (!super.canTarget(controllerId, id, source, game)) { + return false; + } + Card card = game.getCard(id); + return card != null && + this.getTargets() + .stream() + .map(game::getCard) + .mapToInt(Card::getManaValue) + .sum() + card.getManaValue() <= 4; + } +} diff --git a/Mage.Sets/src/mage/cards/l/LivingArtifact.java b/Mage.Sets/src/mage/cards/l/LivingArtifact.java index 2af86b186b4..c4b3c6520d8 100644 --- a/Mage.Sets/src/mage/cards/l/LivingArtifact.java +++ b/Mage.Sets/src/mage/cards/l/LivingArtifact.java @@ -24,7 +24,6 @@ import mage.constants.Zone; import mage.counters.CounterType; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; import mage.target.TargetPermanent; import mage.target.common.TargetArtifactPermanent; @@ -80,7 +79,7 @@ class LivingArtifactTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PLAYER; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PLAYER; } @Override diff --git a/Mage.Sets/src/mage/cards/l/LoanShark.java b/Mage.Sets/src/mage/cards/l/LoanShark.java new file mode 100644 index 00000000000..dd1a1b33d73 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LoanShark.java @@ -0,0 +1,64 @@ +package mage.cards.l; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.keyword.PlotAbility; +import mage.abilities.keyword.StormAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.game.Game; +import mage.watchers.common.SpellsCastWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LoanShark extends CardImpl { + + public LoanShark(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}"); + + this.subtype.add(SubType.SHARK); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // When Loan Shark enters the battlefield, if you've cast two or more spells this turn, draw a card. + this.addAbility(new ConditionalInterveningIfTriggeredAbility( + new EntersBattlefieldTriggeredAbility(new DrawCardSourceControllerEffect(1)), + LoanSharkCondition.instance, "When {this} enters the battlefield, " + + "if you've cast two or more spells this turn, draw a card." + ).addHint(StormAbility.getHint())); + + // Plot {3}{U} + this.addAbility(new PlotAbility("{3}{U}")); + } + + private LoanShark(final LoanShark card) { + super(card); + } + + @Override + public LoanShark copy() { + return new LoanShark(this); + } +} + +enum LoanSharkCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + return game + .getState() + .getWatcher(SpellsCastWatcher.class) + .getCount(source.getControllerId()) >= 2; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/l/LockAndLoad.java b/Mage.Sets/src/mage/cards/l/LockAndLoad.java new file mode 100644 index 00000000000..c249aec13ac --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LockAndLoad.java @@ -0,0 +1,77 @@ +package mage.cards.l; + +import mage.abilities.Ability; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.IntPlusDynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.keyword.PlotAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.game.Game; +import mage.watchers.common.SpellsCastWatcher; + +import java.util.Objects; +import java.util.UUID; + +/** + * @author Susucr + */ +public final class LockAndLoad extends CardImpl { + + private static final DynamicValue xValue = new IntPlusDynamicValue(1, LockAndLoadValue.instance); + + public LockAndLoad(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{U}"); + + // Draw a card, then draw a card for each other instant and sorcery spell you've cast this turn. + this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(xValue) + .setText("Draw a card, then draw a card for each other instant and sorcery spell you've cast this turn")); + + // Plot {3}{U} + this.addAbility(new PlotAbility("{3}{U}")); + } + + private LockAndLoad(final LockAndLoad card) { + super(card); + } + + @Override + public LockAndLoad copy() { + return new LockAndLoad(this); + } +} + +enum LockAndLoadValue implements DynamicValue { + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + SpellsCastWatcher watcher = game.getState().getWatcher(SpellsCastWatcher.class); + return watcher == null ? 0 : + watcher.getSpellsCastThisTurn(sourceAbility.getControllerId()) + .stream() + .filter(Objects::nonNull) + .filter(s -> s.isInstantOrSorcery(game)) + .filter(s -> !s.getSourceId().equals(sourceAbility.getSourceId()) + || s.getZoneChangeCounter(game) != sourceAbility.getSourceObjectZoneChangeCounter()) + .mapToInt(x -> 1) + .sum(); + } + + @Override + public LockAndLoadValue copy() { + return this; + } + + @Override + public String toString() { + return "X"; + } + + @Override + public String getMessage() { + return "Number of other instant and sorcery spell you've cast this turn"; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/l/LocusOfEnlightenment.java b/Mage.Sets/src/mage/cards/l/LocusOfEnlightenment.java index 98e5d355381..46ae8a997a2 100644 --- a/Mage.Sets/src/mage/cards/l/LocusOfEnlightenment.java +++ b/Mage.Sets/src/mage/cards/l/LocusOfEnlightenment.java @@ -85,7 +85,7 @@ class LocusOfEnlightenmentEffect extends ContinuousEffectImpl { if (ability.getAbilityType() == AbilityType.ACTIVATED || ability.getAbilityType() == AbilityType.MANA) { ActivatedAbility copyAbility = (ActivatedAbility) ability.copy(); copyAbility.setMaxActivationsPerTurn(1); - permanent.addAbility(copyAbility, source.getSourceId(), game); + permanent.addAbility(copyAbility, source.getSourceId(), game, true); } } } diff --git a/Mage.Sets/src/mage/cards/l/LonelyArroyo.java b/Mage.Sets/src/mage/cards/l/LonelyArroyo.java new file mode 100644 index 00000000000..38b12ccf027 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LonelyArroyo.java @@ -0,0 +1,48 @@ +package mage.cards.l; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTappedAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.mana.BlueManaAbility; +import mage.abilities.mana.WhiteManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LonelyArroyo extends CardImpl { + + public LonelyArroyo(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + this.subtype.add(SubType.DESERT); + + // Lonely Arroyo enters the battlefield tapped. + this.addAbility(new EntersBattlefieldTappedAbility()); + + // When Lonely Arroyo enters the battlefield, it deals 1 damage to target opponent. + Ability ability = new EntersBattlefieldTriggeredAbility(new DamageTargetEffect(1, "it")); + ability.addTarget(new TargetOpponent()); + this.addAbility(ability); + + // {T}: Add {W} or {U}. + this.addAbility(new WhiteManaAbility()); + this.addAbility(new BlueManaAbility()); + } + + private LonelyArroyo(final LonelyArroyo card) { + super(card); + } + + @Override + public LonelyArroyo copy() { + return new LonelyArroyo(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LonghornSharpshooter.java b/Mage.Sets/src/mage/cards/l/LonghornSharpshooter.java new file mode 100644 index 00000000000..24ce06f80e6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LonghornSharpshooter.java @@ -0,0 +1,50 @@ +package mage.cards.l; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BecomesPlottedSourceTriggeredAbility; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.keyword.PlotAbility; +import mage.abilities.keyword.ReachAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetAnyTarget; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class LonghornSharpshooter extends CardImpl { + + public LonghornSharpshooter(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); + + this.subtype.add(SubType.MINOTAUR); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // When Longhorn Sharpshooter becomes plotted, it deals 2 damage to any target. + Ability ability = new BecomesPlottedSourceTriggeredAbility(new DamageTargetEffect(2)); + ability.addTarget(new TargetAnyTarget()); + this.addAbility(ability); + + // Plot {3}{R} + this.addAbility(new PlotAbility("{3}{R}")); + } + + private LonghornSharpshooter(final LonghornSharpshooter card) { + super(card); + } + + @Override + public LonghornSharpshooter copy() { + return new LonghornSharpshooter(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LootTheKeyToEverything.java b/Mage.Sets/src/mage/cards/l/LootTheKeyToEverything.java new file mode 100644 index 00000000000..68cc299c670 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LootTheKeyToEverything.java @@ -0,0 +1,94 @@ +package mage.cards.l; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.ExileTopXMayPlayUntilEffect; +import mage.abilities.keyword.WardAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterNonlandPermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.game.Game; + +import java.util.Collection; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LootTheKeyToEverything extends CardImpl { + + public LootTheKeyToEverything(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}{U}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.BEAST); + this.subtype.add(SubType.NOBLE); + this.power = new MageInt(1); + this.toughness = new MageInt(2); + + // Ward {1} + this.addAbility(new WardAbility(new ManaCostsImpl<>("{1}"))); + + // At the beginning of your upkeep, exile the top X cards of your library, where X is the number of card types among other nonland permanents you control. You may play those cards this turn. + this.addAbility(new BeginningOfUpkeepTriggeredAbility( + new ExileTopXMayPlayUntilEffect(LootTheKeyToEverythingValue.instance, Duration.EndOfTurn) + .setText("exile the top X cards of your library, where X is the number of card types " + + "among other nonland permanents you control. You may play those cards this turn"), + TargetController.YOU, false + )); + } + + private LootTheKeyToEverything(final LootTheKeyToEverything card) { + super(card); + } + + @Override + public LootTheKeyToEverything copy() { + return new LootTheKeyToEverything(this); + } +} + +enum LootTheKeyToEverythingValue implements DynamicValue { + instance; + private static final FilterPermanent filter = new FilterNonlandPermanent(); + + static { + filter.add(AnotherPredicate.instance); + filter.add(TargetController.YOU.getControllerPredicate()); + } + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return game + .getBattlefield() + .getActivePermanents(filter, sourceAbility.getControllerId(), sourceAbility, game) + .stream() + .map(permanent -> permanent.getCardType(game)) + .flatMap(Collection::stream) + .distinct() + .mapToInt(x -> 1) + .sum(); + } + + @Override + public LootTheKeyToEverythingValue copy() { + return this; + } + + @Override + public String getMessage() { + return ""; + } + + @Override + public String toString() { + return "X"; + } +} diff --git a/Mage.Sets/src/mage/cards/l/LostJitte.java b/Mage.Sets/src/mage/cards/l/LostJitte.java new file mode 100644 index 00000000000..30486212b45 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LostJitte.java @@ -0,0 +1,63 @@ +package mage.cards.l; + +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.common.DealsCombatDamageEquippedTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.RemoveCountersSourceCost; +import mage.abilities.effects.common.UntapTargetEffect; +import mage.abilities.effects.common.combat.CantBlockTargetEffect; +import mage.abilities.effects.common.counter.AddCountersAttachedEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.EquipAbility; +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.target.common.TargetCreaturePermanent; +import mage.target.common.TargetLandPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LostJitte extends CardImpl { + + public LostJitte(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.EQUIPMENT); + + // Whenever equipped creature deals combat damage, put a charge counter on Lost Jitte. + this.addAbility(new DealsCombatDamageEquippedTriggeredAbility(new AddCountersSourceEffect(CounterType.CHARGE.createInstance()))); + + // Remove a charge counter from Lost Jitte: Choose one -- + // * Untap target land. + Ability ability = new SimpleActivatedAbility(new UntapTargetEffect(), new RemoveCountersSourceCost(CounterType.CHARGE.createInstance())); + ability.addTarget(new TargetLandPermanent()); + + // * Target creature can't block this turn. + ability.addMode(new Mode(new CantBlockTargetEffect(Duration.EndOfTurn)).addTarget(new TargetCreaturePermanent())); + + // * Put a +1/+1 counter on equipped creature. + ability.addMode(new Mode(new AddCountersAttachedEffect(CounterType.P1P1.createInstance(), "equipped creature"))); + this.addAbility(ability); + + // Equip {1} + this.addAbility(new EquipAbility(1)); + } + + private LostJitte(final LostJitte card) { + super(card); + } + + @Override + public LostJitte copy() { + return new LostJitte(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LotusRing.java b/Mage.Sets/src/mage/cards/l/LotusRing.java new file mode 100644 index 00000000000..7a9d7132b8d --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LotusRing.java @@ -0,0 +1,62 @@ +package mage.cards.l; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.effects.common.continuous.BoostEquippedEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.effects.mana.AddManaOfAnyColorEffect; +import mage.abilities.keyword.EquipAbility; +import mage.abilities.keyword.IndestructibleAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.abilities.mana.SimpleManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AttachmentType; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Zone; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LotusRing extends CardImpl { + + public LotusRing(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}"); + + this.subtype.add(SubType.EQUIPMENT); + + // Indestructible + this.addAbility(IndestructibleAbility.getInstance()); + + // Equipped creature gets +3/+3 and has vigilance and "{T}, Sacrifice this creature: Add three mana of any one color." + Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(3, 3)); + ability.addEffect(new GainAbilityAttachedEffect( + VigilanceAbility.getInstance(), AttachmentType.EQUIPMENT + ).setText("and has vigilance")); + Ability manaAbility = new SimpleManaAbility( + Zone.BATTLEFIELD, new AddManaOfAnyColorEffect(3), new TapSourceCost() + ); + manaAbility.addCost(new SacrificeSourceCost().setText("sacrifice this creature")); + ability.addEffect(new GainAbilityAttachedEffect( + manaAbility, AttachmentType.EQUIPMENT + ).setText("and \"{T}, Sacrifice this creature: Add three mana of any one color.\"")); + this.addAbility(ability); + + // Equip {3} + this.addAbility(new EquipAbility(3)); + } + + private LotusRing(final LotusRing card) { + super(card); + } + + @Override + public LotusRing copy() { + return new LotusRing(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LuminatePrimordial.java b/Mage.Sets/src/mage/cards/l/LuminatePrimordial.java index f0e2304b9cc..3d4d1e50d63 100644 --- a/Mage.Sets/src/mage/cards/l/LuminatePrimordial.java +++ b/Mage.Sets/src/mage/cards/l/LuminatePrimordial.java @@ -11,15 +11,13 @@ import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterCreaturePermanent; -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.Target; import mage.target.common.TargetCreaturePermanent; -import mage.target.targetadjustment.TargetAdjuster; +import mage.target.targetadjustment.EachOpponentPermanentTargetsAdjuster; import java.util.*; import java.util.stream.Collectors; @@ -42,7 +40,7 @@ public final class LuminatePrimordial extends CardImpl { // When Luminate Primordial enters the battlefield, for each opponent, exile up to one target creature // that player controls and that player gains life equal to its power. Ability ability = new EntersBattlefieldTriggeredAbility(new LuminatePrimordialEffect(), false); - ability.setTargetAdjuster(LuminatePrimordialAdjuster.instance); + ability.setTargetAdjuster(new EachOpponentPermanentTargetsAdjuster(new TargetCreaturePermanent(0,1))); this.addAbility(ability); } @@ -56,24 +54,6 @@ public final class LuminatePrimordial extends CardImpl { } } -enum LuminatePrimordialAdjuster implements TargetAdjuster { - instance; - - @Override - public void adjustTargets(Ability ability, Game game) { - ability.getTargets().clear(); - for (UUID opponentId : game.getOpponents(ability.getControllerId())) { - Player opponent = game.getPlayer(opponentId); - if (opponent != null) { - FilterCreaturePermanent filter = new FilterCreaturePermanent("creature from opponent " + opponent.getLogName()); - filter.add(new ControllerIdPredicate(opponentId)); - TargetCreaturePermanent target = new TargetCreaturePermanent(0, 1, filter, false); - ability.addTarget(target); - } - } - } -} - class LuminatePrimordialEffect extends OneShotEffect { LuminatePrimordialEffect() { diff --git a/Mage.Sets/src/mage/cards/l/LurrusOfTheDreamDen.java b/Mage.Sets/src/mage/cards/l/LurrusOfTheDreamDen.java index 2cf85ac35c8..dece80f0725 100644 --- a/Mage.Sets/src/mage/cards/l/LurrusOfTheDreamDen.java +++ b/Mage.Sets/src/mage/cards/l/LurrusOfTheDreamDen.java @@ -2,7 +2,7 @@ package mage.cards.l; import mage.MageInt; import mage.MageObject; -import mage.abilities.common.CastFromGraveyardOnceStaticAbility; +import mage.abilities.common.CastFromGraveyardOnceEachTurnAbility; import mage.abilities.keyword.CompanionAbility; import mage.abilities.keyword.CompanionCondition; import mage.abilities.keyword.LifelinkAbility; @@ -24,7 +24,7 @@ import java.util.UUID; */ public final class LurrusOfTheDreamDen extends CardImpl { - private static final FilterPermanentCard filter = new FilterPermanentCard(); + private static final FilterPermanentCard filter = new FilterPermanentCard("a permanent spell with mana value 2 or less"); static { filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, 3)); @@ -46,7 +46,7 @@ public final class LurrusOfTheDreamDen extends CardImpl { this.addAbility(LifelinkAbility.getInstance()); // During each of your turns, you may cast one permanent spell with converted mana cost 2 or less from your graveyard. - this.addAbility(new CastFromGraveyardOnceStaticAbility(filter, "During each of your turns, you may cast one permanent spell with mana value 2 or less from your graveyard")); + this.addAbility(new CastFromGraveyardOnceEachTurnAbility(filter)); } private LurrusOfTheDreamDen(final LurrusOfTheDreamDen card) { diff --git a/Mage.Sets/src/mage/cards/l/LushOasis.java b/Mage.Sets/src/mage/cards/l/LushOasis.java new file mode 100644 index 00000000000..070ed046a6e --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LushOasis.java @@ -0,0 +1,48 @@ +package mage.cards.l; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTappedAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.mana.BlueManaAbility; +import mage.abilities.mana.GreenManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LushOasis extends CardImpl { + + public LushOasis(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + this.subtype.add(SubType.DESERT); + + // Lush Oasis enters the battlefield tapped. + this.addAbility(new EntersBattlefieldTappedAbility()); + + // When Lush Oasis enters the battlefield, it deals 1 damage to target opponent. + Ability ability = new EntersBattlefieldTriggeredAbility(new DamageTargetEffect(1, "it")); + ability.addTarget(new TargetOpponent()); + this.addAbility(ability); + + // {T}: Add {G} or {U}. + this.addAbility(new GreenManaAbility()); + this.addAbility(new BlueManaAbility()); + } + + private LushOasis(final LushOasis card) { + super(card); + } + + @Override + public LushOasis copy() { + return new LushOasis(this); + } +} diff --git a/Mage.Sets/src/mage/cards/l/LuxuriousLocomotive.java b/Mage.Sets/src/mage/cards/l/LuxuriousLocomotive.java new file mode 100644 index 00000000000..ea128b5dcce --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LuxuriousLocomotive.java @@ -0,0 +1,76 @@ +package mage.cards.l; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.ActivatedAbility; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.CrewAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.game.Game; +import mage.game.permanent.token.TreasureToken; +import mage.watchers.common.CrewedVehicleWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LuxuriousLocomotive extends CardImpl { + + public LuxuriousLocomotive(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{5}"); + + this.subtype.add(SubType.VEHICLE); + this.power = new MageInt(6); + this.toughness = new MageInt(5); + + // Whenever Luxurious Locomotive attacks, create a Treasure token for each creature that crewed it this turn. + this.addAbility(new AttacksTriggeredAbility(new CreateTokenEffect( + new TreasureToken(), LuxuriousLocomotiveValue.instance + )), new CrewedVehicleWatcher()); + + // Crew 1. Activate only once each turn. + ActivatedAbility ability = new CrewAbility(1); + ability.setMaxActivationsPerTurn(1); + this.addAbility(ability); + } + + private LuxuriousLocomotive(final LuxuriousLocomotive card) { + super(card); + } + + @Override + public LuxuriousLocomotive copy() { + return new LuxuriousLocomotive(this); + } +} + +enum LuxuriousLocomotiveValue implements DynamicValue { + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return CrewedVehicleWatcher.getCrewCount(sourceAbility.getSourcePermanentOrLKI(game), game); + } + + @Override + public LuxuriousLocomotiveValue copy() { + return this; + } + + @Override + public String getMessage() { + return "creature that crewed it this turn"; + } + + @Override + public String toString() { + return "1"; + } +} diff --git a/Mage.Sets/src/mage/cards/m/MaestrosAscendancy.java b/Mage.Sets/src/mage/cards/m/MaestrosAscendancy.java index 7458014e2c7..6989af5719d 100644 --- a/Mage.Sets/src/mage/cards/m/MaestrosAscendancy.java +++ b/Mage.Sets/src/mage/cards/m/MaestrosAscendancy.java @@ -18,6 +18,7 @@ import mage.filter.StaticFilters; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.ZoneChangeEvent; +import mage.game.stack.Spell; import mage.players.Player; import mage.watchers.Watcher; @@ -83,7 +84,7 @@ class MaestrosAscendancyCastEffect extends AsThoughEffectImpl { || !card.isOwnedBy(affectedControllerId) || !card.isInstantOrSorcery(game) || !game.getState().getZone(objectId).match(Zone.GRAVEYARD) - || !MaestrosAscendancyWatcher.checkPlayer(source, game)) { + || MaestrosAscendancyWatcher.checkPlayer(source, game)) { return false; } Costs newCosts = new CostsImpl<>(); @@ -115,10 +116,8 @@ class MaestrosAscendancyExileEffect extends ReplacementEffectImpl { @Override public boolean replaceEvent(GameEvent event, Ability source, Game game) { - Player controller = game.getPlayer(source.getControllerId()); - Card card = game.getCard(event.getTargetId()); - return controller != null && card != null - && controller.moveCards(card, Zone.EXILED, source, game); + ((ZoneChangeEvent) event).setToZone(Zone.EXILED); + return false; } @Override @@ -129,8 +128,10 @@ class MaestrosAscendancyExileEffect extends ReplacementEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { ZoneChangeEvent zEvent = (ZoneChangeEvent) event; + Spell spell = game.getSpellOrLKIStack(zEvent.getTargetId()); return zEvent.getToZone() == Zone.GRAVEYARD - && MaestrosAscendancyWatcher.checkSpell(zEvent.getTargetId(), source, game); + && spell != null + && MaestrosAscendancyWatcher.checkSpell(spell, source, game); } } @@ -146,6 +147,7 @@ class MaestrosAscendancyWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { if (event.getType() == GameEvent.EventType.SPELL_CAST + && event.hasApprovingIdentifier(MageIdentifier.MaestrosAscendencyAlternateCast) && event.getAdditionalReference() != null) { playerMap.computeIfAbsent( event.getAdditionalReference() @@ -172,16 +174,16 @@ class MaestrosAscendancyWatcher extends Watcher { .getState() .getWatcher(MaestrosAscendancyWatcher.class) .playerMap - .getOrDefault(new MageObjectReference(source), Collections.emptySet()) + .getOrDefault(new MageObjectReference(source.getSourcePermanentIfItStillExists(game), game), Collections.emptySet()) .contains(source.getControllerId()); } - static boolean checkSpell(UUID id, Ability source, Game game) { + static boolean checkSpell(Spell spell, Ability source, Game game) { return game .getState() .getWatcher(MaestrosAscendancyWatcher.class) .spellMap - .getOrDefault(new MageObjectReference(source), Collections.emptySet()) - .contains(new MageObjectReference(id, game)); + .getOrDefault(new MageObjectReference(source.getSourcePermanentOrLKI(game), game), Collections.emptySet()) + .contains(new MageObjectReference(spell, game)); } } diff --git a/Mage.Sets/src/mage/cards/m/MagdaTheHoardmaster.java b/Mage.Sets/src/mage/cards/m/MagdaTheHoardmaster.java new file mode 100644 index 00000000000..28e11f98f24 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MagdaTheHoardmaster.java @@ -0,0 +1,56 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.common.CommittedCrimeTriggeredAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.common.FilterControlledPermanent; +import mage.game.permanent.token.ScorpionDragonToken; +import mage.game.permanent.token.TreasureToken; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class MagdaTheHoardmaster extends CardImpl { + + private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.TREASURE, "Treasures"); + + public MagdaTheHoardmaster(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.DWARF); + this.subtype.add(SubType.BERSERKER); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Whenever you commit a crime, create a tapped Treasure token. This ability triggers only once each turn. + this.addAbility( + new CommittedCrimeTriggeredAbility(new CreateTokenEffect(new TreasureToken(), 1, true)) + .setTriggersOnceEachTurn(true) + ); + + // Sacrifice three Treasures: Create a 4/4 red Scorpion Dragon creature token with flying and haste. Activate only as a sorcery. + this.addAbility(new ActivateAsSorceryActivatedAbility( + new CreateTokenEffect(new ScorpionDragonToken()), + new SacrificeTargetCost(3, filter) + )); + } + + private MagdaTheHoardmaster(final MagdaTheHoardmaster card) { + super(card); + } + + @Override + public MagdaTheHoardmaster copy() { + return new MagdaTheHoardmaster(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MagebaneLizard.java b/Mage.Sets/src/mage/cards/m/MagebaneLizard.java new file mode 100644 index 00000000000..782ccfa60c4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MagebaneLizard.java @@ -0,0 +1,80 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SpellCastAllTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +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.StaticFilters; +import mage.game.Game; +import mage.players.Player; +import mage.watchers.common.SpellsCastWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MagebaneLizard extends CardImpl { + + public MagebaneLizard(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}"); + + this.subtype.add(SubType.LIZARD); + this.power = new MageInt(1); + this.toughness = new MageInt(4); + + // Whenever a player casts a noncreature spell, Magebane Lizard deals damage to that player equal to the number of noncreature spells they've cast this turn. + this.addAbility(new SpellCastAllTriggeredAbility( + new MagebaneLizardEffect(), StaticFilters.FILTER_SPELL_A_NON_CREATURE, + false, SetTargetPointer.PLAYER + )); + } + + private MagebaneLizard(final MagebaneLizard card) { + super(card); + } + + @Override + public MagebaneLizard copy() { + return new MagebaneLizard(this); + } +} + +class MagebaneLizardEffect extends OneShotEffect { + + MagebaneLizardEffect() { + super(Outcome.Benefit); + staticText = "{this} deals damage to that player equal to the number of noncreature spells they've cast this turn"; + } + + private MagebaneLizardEffect(final MagebaneLizardEffect effect) { + super(effect); + } + + @Override + public MagebaneLizardEffect copy() { + return new MagebaneLizardEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); + if (player == null) { + return false; + } + int count = game + .getState() + .getWatcher(SpellsCastWatcher.class) + .getSpellsCastThisTurn(player.getId()) + .stream() + .mapToInt(spell -> !spell.isCreature(game) ? 1 : 0) + .sum(); + return player.damage(count, source, game) > 0; + } +} diff --git a/Mage.Sets/src/mage/cards/m/MagmaticGalleon.java b/Mage.Sets/src/mage/cards/m/MagmaticGalleon.java index 57d3ee069a4..0b1019cd047 100644 --- a/Mage.Sets/src/mage/cards/m/MagmaticGalleon.java +++ b/Mage.Sets/src/mage/cards/m/MagmaticGalleon.java @@ -13,7 +13,7 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; import mage.game.Game; -import mage.game.events.DamagedBatchEvent; +import mage.game.events.DamagedBatchForPermanentsEvent; import mage.game.events.DamagedEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; @@ -80,8 +80,7 @@ class MagmaticGalleonTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { - DamagedBatchEvent dEvent = (DamagedBatchEvent) event; - int damage = dEvent + int damage = ((DamagedBatchForPermanentsEvent) event) .getEvents() .stream() .filter(damagedEvent -> { diff --git a/Mage.Sets/src/mage/cards/m/MagusOfTheFuture.java b/Mage.Sets/src/mage/cards/m/MagusOfTheFuture.java index f03135109c7..a5988407338 100644 --- a/Mage.Sets/src/mage/cards/m/MagusOfTheFuture.java +++ b/Mage.Sets/src/mage/cards/m/MagusOfTheFuture.java @@ -2,7 +2,7 @@ package mage.cards.m; import mage.MageInt; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.common.continuous.PlayTheTopCardEffect; +import mage.abilities.effects.common.continuous.PlayFromTopOfLibraryEffect; import mage.abilities.effects.common.continuous.PlayWithTheTopCardRevealedEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -29,7 +29,7 @@ public final class MagusOfTheFuture extends CardImpl { this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PlayWithTheTopCardRevealedEffect())); // You may play lands and cast spells from the top of your library. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PlayTheTopCardEffect())); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PlayFromTopOfLibraryEffect())); } private MagusOfTheFuture(final MagusOfTheFuture card) { diff --git a/Mage.Sets/src/mage/cards/m/MakeYourOwnLuck.java b/Mage.Sets/src/mage/cards/m/MakeYourOwnLuck.java new file mode 100644 index 00000000000..7cf63157cdf --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MakeYourOwnLuck.java @@ -0,0 +1,67 @@ +package mage.cards.m; + +import mage.abilities.Ability; +import mage.abilities.effects.common.LookLibraryAndPickControllerEffect; +import mage.abilities.keyword.PlotAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.constants.CardType; +import mage.constants.PutCards; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class MakeYourOwnLuck extends CardImpl { + + public MakeYourOwnLuck(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{G}{U}"); + + // Look at the top three cards of your library. You may exile a nonland card from among them. If you do, it becomes plotted. Put the rest into your hand. + this.getSpellAbility().addEffect(new MakeYourOwnLuckEffect()); + } + + private MakeYourOwnLuck(final MakeYourOwnLuck card) { + super(card); + } + + @Override + public MakeYourOwnLuck copy() { + return new MakeYourOwnLuck(this); + } +} + +class MakeYourOwnLuckEffect extends LookLibraryAndPickControllerEffect { + + MakeYourOwnLuckEffect() { + super(3, 1, StaticFilters.FILTER_CARD_NON_LAND, PutCards.EXILED, PutCards.HAND); + staticText = "Look at the top three cards of your library. " + + "You may exile a nonland card from among them. " + + "If you do, it becomes plotted. Put the rest into your hand"; + } + + private MakeYourOwnLuckEffect(final MakeYourOwnLuckEffect effect) { + super(effect); + } + + @Override + public MakeYourOwnLuckEffect copy() { + return new MakeYourOwnLuckEffect(this); + } + + @Override + public boolean actionWithPickedCards(Game game, Ability source, Player player, Cards pickedCards, Cards otherCards) { + boolean result = false; + for (Card card : pickedCards.getCards(game)) { + result |= PlotAbility.doExileAndPlotCard(card, game, source); + } + result |= putLookedCards.moveCards(player, otherCards, source, game); + return result; + } +} diff --git a/Mage.Sets/src/mage/cards/m/MalcolmTheEyes.java b/Mage.Sets/src/mage/cards/m/MalcolmTheEyes.java new file mode 100644 index 00000000000..cf7ffdaa5cc --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MalcolmTheEyes.java @@ -0,0 +1,48 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.common.CastSecondSpellTriggeredAbility; +import mage.abilities.effects.keyword.InvestigateEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.HasteAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MalcolmTheEyes extends CardImpl { + + public MalcolmTheEyes(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{U}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SIREN); + this.subtype.add(SubType.PIRATE); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // Whenever you cast your second spell each turn, investigate. + this.addAbility(new CastSecondSpellTriggeredAbility(new InvestigateEffect())); + } + + private MalcolmTheEyes(final MalcolmTheEyes card) { + super(card); + } + + @Override + public MalcolmTheEyes copy() { + return new MalcolmTheEyes(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MapTheFrontier.java b/Mage.Sets/src/mage/cards/m/MapTheFrontier.java new file mode 100644 index 00000000000..c7063e87c43 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MapTheFrontier.java @@ -0,0 +1,46 @@ +package mage.cards.m; + +import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; +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.Predicates; +import mage.target.common.TargetCardInLibrary; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MapTheFrontier extends CardImpl { + + private static final FilterCard filter = new FilterCard("basic land cards and/or Desert cards"); + + static { + filter.add(Predicates.or( + Predicates.and( + SuperType.BASIC.getPredicate(), + CardType.LAND.getPredicate() + ), SubType.DESERT.getPredicate() + )); + } + + public MapTheFrontier(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{G}"); + + // Search your library for up to two basic land cards and/or Desert cards, put them onto the battlefield tapped, then shuffle. + this.getSpellAbility().addEffect(new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(0, 2, filter), true)); + } + + private MapTheFrontier(final MapTheFrontier card) { + super(card); + } + + @Override + public MapTheFrontier copy() { + return new MapTheFrontier(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MaraudingSphinx.java b/Mage.Sets/src/mage/cards/m/MaraudingSphinx.java new file mode 100644 index 00000000000..13a830aeebf --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MaraudingSphinx.java @@ -0,0 +1,51 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.common.CommittedCrimeTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.keyword.SurveilEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.abilities.keyword.WardAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MaraudingSphinx extends CardImpl { + + public MaraudingSphinx(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}{U}"); + + this.subtype.add(SubType.SPHINX); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(3); + this.toughness = new MageInt(5); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Ward {2} + this.addAbility(new WardAbility(new ManaCostsImpl<>("{2}"), false)); + + // Whenever you commit a crime, surveil 2. This ability triggers only once each turn. + this.addAbility(new CommittedCrimeTriggeredAbility(new SurveilEffect(2), false).setTriggersOnceEachTurn(true)); + } + + private MaraudingSphinx(final MaraudingSphinx card) { + super(card); + } + + @Override + public MaraudingSphinx copy() { + return new MaraudingSphinx(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MarchesaDealerOfDeath.java b/Mage.Sets/src/mage/cards/m/MarchesaDealerOfDeath.java new file mode 100644 index 00000000000..252b27f19fe --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MarchesaDealerOfDeath.java @@ -0,0 +1,47 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.common.CommittedCrimeTriggeredAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.LookLibraryAndPickControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.PutCards; +import mage.constants.SubType; +import mage.constants.SuperType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MarchesaDealerOfDeath extends CardImpl { + + public MarchesaDealerOfDeath(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{U}{B}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // Whenever you commit a crime, you may pay {1}. If you do, look at the top two cards of your library. Put one of them into your hand and the other into your graveyard. + this.addAbility(new CommittedCrimeTriggeredAbility(new DoIfCostPaid( + new LookLibraryAndPickControllerEffect( + 2, 1, PutCards.HAND, PutCards.GRAVEYARD + ), new GenericManaCost(1) + ))); + } + + private MarchesaDealerOfDeath(final MarchesaDealerOfDeath card) { + super(card); + } + + @Override + public MarchesaDealerOfDeath copy() { + return new MarchesaDealerOfDeath(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MassMutiny.java b/Mage.Sets/src/mage/cards/m/MassMutiny.java index afad5d93d6a..a686ced3c0e 100644 --- a/Mage.Sets/src/mage/cards/m/MassMutiny.java +++ b/Mage.Sets/src/mage/cards/m/MassMutiny.java @@ -1,6 +1,5 @@ package mage.cards.m; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.OneShotEffect; @@ -12,16 +11,15 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Outcome; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.permanent.ControllerIdPredicate; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.players.Player; import mage.target.Target; import mage.target.common.TargetCreaturePermanent; -import mage.target.targetadjustment.TargetAdjuster; +import mage.target.targetadjustment.EachOpponentPermanentTargetsAdjuster; import mage.target.targetpointer.FixedTarget; +import java.util.UUID; + /** * * @author LevelX2 @@ -33,7 +31,7 @@ public final class MassMutiny extends CardImpl { // For each opponent, gain control of up to one target creature that player controls until end of turn. Untap those creatures. They gain haste until end of turn. this.getSpellAbility().addEffect(new MassMutinyEffect()); - this.getSpellAbility().setTargetAdjuster(MassMutinyAdjuster.instance); + this.getSpellAbility().setTargetAdjuster(new EachOpponentPermanentTargetsAdjuster(new TargetCreaturePermanent(0,1))); } private MassMutiny(final MassMutiny card) { @@ -46,24 +44,6 @@ public final class MassMutiny extends CardImpl { } } -enum MassMutinyAdjuster implements TargetAdjuster { - instance; - - @Override - public void adjustTargets(Ability ability, Game game) { - ability.getTargets().clear(); - for (UUID opponentId : game.getOpponents(ability.getControllerId())) { - Player opponent = game.getPlayer(opponentId); - if (opponent != null) { - FilterCreaturePermanent filter = new FilterCreaturePermanent("creature from opponent " + opponent.getName()); - filter.add(new ControllerIdPredicate(opponentId)); - TargetCreaturePermanent target = new TargetCreaturePermanent(0, 1, filter, false); - ability.addTarget(target); - } - } - } -} - class MassMutinyEffect extends OneShotEffect { MassMutinyEffect() { diff --git a/Mage.Sets/src/mage/cards/m/MavindaStudentsAdvocate.java b/Mage.Sets/src/mage/cards/m/MavindaStudentsAdvocate.java index 4b1cd54c2a6..0d2b2510f0f 100644 --- a/Mage.Sets/src/mage/cards/m/MavindaStudentsAdvocate.java +++ b/Mage.Sets/src/mage/cards/m/MavindaStudentsAdvocate.java @@ -6,7 +6,7 @@ import mage.abilities.Ability; import mage.abilities.SpellAbility; import mage.abilities.common.LimitedTimesPerTurnActivatedAbility; import mage.abilities.costs.mana.GenericManaCost; -import mage.abilities.effects.common.MayCastTargetThenExileEffect; +import mage.abilities.effects.common.MayCastTargetCardEffect; import mage.abilities.effects.common.cost.CostModificationEffectImpl; import mage.abilities.keyword.FlyingAbility; import mage.cards.Card; @@ -61,10 +61,10 @@ public final class MavindaStudentsAdvocate extends CardImpl { } } -class MavindaStudentsAdvocateEffect extends MayCastTargetThenExileEffect { +class MavindaStudentsAdvocateEffect extends MayCastTargetCardEffect { MavindaStudentsAdvocateEffect() { - super(Duration.EndOfTurn); + super(Duration.EndOfTurn, true); staticText = "you may cast target instant or sorcery card from your graveyard this turn. " + "If that spell doesn't target a creature you control, it costs {8} more to cast this way. " + "If that spell would be put into your graveyard, exile it instead"; diff --git a/Mage.Sets/src/mage/cards/m/MelekIzzetParagon.java b/Mage.Sets/src/mage/cards/m/MelekIzzetParagon.java index e878280b28e..a98f111a506 100644 --- a/Mage.Sets/src/mage/cards/m/MelekIzzetParagon.java +++ b/Mage.Sets/src/mage/cards/m/MelekIzzetParagon.java @@ -5,7 +5,7 @@ import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.Effect; import mage.abilities.effects.common.CopyTargetSpellEffect; -import mage.abilities.effects.common.continuous.PlayTheTopCardEffect; +import mage.abilities.effects.common.continuous.PlayFromTopOfLibraryEffect; import mage.abilities.effects.common.continuous.PlayWithTheTopCardRevealedEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -46,7 +46,7 @@ public final class MelekIzzetParagon extends CardImpl { this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PlayWithTheTopCardRevealedEffect())); // You may cast instant and sorcery spells from the top of your library. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PlayTheTopCardEffect(TargetController.YOU, filter, false))); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PlayFromTopOfLibraryEffect(filter))); // Whenever you cast an instant or sorcery spell from your library, copy it. You may choose new targets for the copy. this.addAbility(new MelekIzzetParagonTriggeredAbility()); diff --git a/Mage.Sets/src/mage/cards/m/MemoryPlunder.java b/Mage.Sets/src/mage/cards/m/MemoryPlunder.java index a7760a9dd87..5a5e67f686e 100644 --- a/Mage.Sets/src/mage/cards/m/MemoryPlunder.java +++ b/Mage.Sets/src/mage/cards/m/MemoryPlunder.java @@ -1,23 +1,17 @@ package mage.cards.m; -import java.util.UUID; -import mage.ApprovingObject; -import mage.abilities.Ability; -import mage.abilities.effects.OneShotEffect; -import mage.cards.Card; +import mage.abilities.effects.common.MayCastTargetCardEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.Zone; +import mage.constants.CastManaAdjustment; import mage.filter.FilterCard; import mage.filter.predicate.Predicates; -import mage.game.Game; -import mage.players.Player; import mage.target.common.TargetCardInOpponentsGraveyard; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class MemoryPlunder extends CardImpl { @@ -34,7 +28,7 @@ public final class MemoryPlunder extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{U/B}{U/B}{U/B}{U/B}"); // You may cast target instant or sorcery card from an opponent's graveyard without paying its mana cost. - this.getSpellAbility().addEffect(new MemoryPlunderEffect()); + this.getSpellAbility().addEffect(new MayCastTargetCardEffect(CastManaAdjustment.WITHOUT_PAYING_MANA_COST)); this.getSpellAbility().addTarget(new TargetCardInOpponentsGraveyard(filter)); } @@ -47,40 +41,4 @@ public final class MemoryPlunder extends CardImpl { public MemoryPlunder copy() { return new MemoryPlunder(this); } -} - -class MemoryPlunderEffect extends OneShotEffect { - - MemoryPlunderEffect() { - super(Outcome.PlayForFree); - this.staticText = "You may cast target instant or sorcery card from " - + "an opponent's graveyard without paying its mana cost"; - } - - private MemoryPlunderEffect(final MemoryPlunderEffect effect) { - super(effect); - } - - @Override - public MemoryPlunderEffect copy() { - return new MemoryPlunderEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Card card = game.getCard(getTargetPointer().getFirst(game, source)); - if (card != null) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null - && game.getState().getZone(card.getId()) == Zone.GRAVEYARD - && controller.chooseUse(Outcome.PlayForFree, "Cast " + card.getName() + " without paying cost?", source, game)) { - game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE); - Boolean cardWasCast = controller.cast(controller.chooseAbilityForCast(card, game, true), - game, true, new ApprovingObject(source, game)); - game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null); - return cardWasCast; - } - } - return false; - } -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/m/MemoryVessel.java b/Mage.Sets/src/mage/cards/m/MemoryVessel.java new file mode 100644 index 00000000000..5ef37794587 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MemoryVessel.java @@ -0,0 +1,113 @@ +package mage.cards.m; + +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.costs.common.ExileSourceCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; +import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.players.Player; +import mage.util.CardUtil; + +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MemoryVessel extends CardImpl { + + public MemoryVessel(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}{R}{R}"); + + // {T}, Exile Memory Vessel: Each player exiles the top seven cards of their library. Until your next turn, players may play cards they exiled this way, and they can't play cards from their hand. Activate only as a sorcery. + Ability ability = new ActivateAsSorceryActivatedAbility(new MemoryVesselExileEffect(), new TapSourceCost()); + ability.addCost(new ExileSourceCost()); + ability.addEffect(new MemoryVesselPreventionEffect()); + this.addAbility(ability); + } + + private MemoryVessel(final MemoryVessel card) { + super(card); + } + + @Override + public MemoryVessel copy() { + return new MemoryVessel(this); + } +} + +class MemoryVesselExileEffect extends OneShotEffect { + + MemoryVesselExileEffect() { + super(Outcome.Benefit); + staticText = "each player exiles the top seven cards of their library. " + + "Until your next turn, players may play cards they exiled this way"; + } + + private MemoryVesselExileEffect(final MemoryVesselExileEffect effect) { + super(effect); + } + + @Override + public MemoryVesselExileEffect copy() { + return new MemoryVesselExileEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { + Player player = game.getPlayer(playerId); + if (player == null) { + continue; + } + Set cards = player.getLibrary().getTopCards(game, 7); + player.moveCards(cards, Zone.EXILED, source, game); + for (Card card : cards) { + CardUtil.makeCardPlayable( + game, source, card, Duration.UntilYourNextTurn, + false, playerId, null + ); + } + } + return true; + } +} + +class MemoryVesselPreventionEffect extends ContinuousRuleModifyingEffectImpl { + + public MemoryVesselPreventionEffect() { + super(Duration.UntilYourNextTurn, Outcome.Benefit); + staticText = ", and they can't play cards from their hand"; + } + + private MemoryVesselPreventionEffect(final MemoryVesselPreventionEffect effect) { + super(effect); + } + + @Override + public MemoryVesselPreventionEffect copy() { + return new MemoryVesselPreventionEffect(this); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.CAST_SPELL + || event.getType() == GameEvent.EventType.PLAY_LAND; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + Card card = game.getCard(event.getSourceId()); + return card != null && Zone.HAND.match(game.getState().getZone(card.getId())); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MetamorphicBlast.java b/Mage.Sets/src/mage/cards/m/MetamorphicBlast.java new file mode 100644 index 00000000000..6ad8f0b793e --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MetamorphicBlast.java @@ -0,0 +1,52 @@ +package mage.cards.m; + +import mage.abilities.Mode; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.DrawCardTargetEffect; +import mage.abilities.effects.common.continuous.BecomesCreatureTargetEffect; +import mage.abilities.keyword.SpreeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.game.permanent.token.custom.CreatureToken; +import mage.target.TargetPlayer; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MetamorphicBlast extends CardImpl { + + public MetamorphicBlast(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{U}"); + + // Spree + this.addAbility(new SpreeAbility(this)); + + // + {1} -- Until end of turn, target creature becomes a white Rabbit with base power and toughness 0/1. + this.getSpellAbility().addEffect(new BecomesCreatureTargetEffect(new CreatureToken( + 0, 1, "white Rabbit with base power and toughness 0/1" + ).withSubType(SubType.RABBIT).withColor("W"), false, false, Duration.EndOfTurn) + .withDurationRuleAtStart(true)); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + this.getSpellAbility().withFirstModeCost(new GenericManaCost(1)); + + // + {3} -- Target player draws two cards. + this.getSpellAbility().addMode(new Mode(new DrawCardTargetEffect(2)) + .addTarget(new TargetPlayer()) + .withCost(new GenericManaCost(3))); + } + + private MetamorphicBlast(final MetamorphicBlast card) { + super(card); + } + + @Override + public MetamorphicBlast copy() { + return new MetamorphicBlast(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MindbladeRender.java b/Mage.Sets/src/mage/cards/m/MindbladeRender.java index 37fcea8888b..cd23225d419 100644 --- a/Mage.Sets/src/mage/cards/m/MindbladeRender.java +++ b/Mage.Sets/src/mage/cards/m/MindbladeRender.java @@ -1,21 +1,22 @@ package mage.cards.m; -import java.util.UUID; import mage.MageInt; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.LoseLifeSourceControllerEffect; -import mage.constants.SubType; 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.DamagedPlayerEvent; +import mage.game.events.DamagedBatchForOnePlayerEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.players.Player; +import java.util.UUID; + /** * * @author TheElk801 @@ -46,17 +47,13 @@ public final class MindbladeRender extends CardImpl { class MindbladeRenderTriggeredAbility extends TriggeredAbilityImpl { - private boolean usedForCombatDamageStep; - public MindbladeRenderTriggeredAbility() { super(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1)); this.addEffect(new LoseLifeSourceControllerEffect(1)); - this.usedForCombatDamageStep = false; } private MindbladeRenderTriggeredAbility(final MindbladeRenderTriggeredAbility effect) { super(effect); - this.usedForCombatDamageStep = effect.usedForCombatDamageStep; } @Override @@ -66,34 +63,34 @@ class MindbladeRenderTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PLAYER || event.getType() == GameEvent.EventType.COMBAT_DAMAGE_STEP_POST; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PLAYER; } @Override public boolean checkTrigger(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.COMBAT_DAMAGE_STEP_POST) { - usedForCombatDamageStep = false; - return false; - } - if (event.getType() != GameEvent.EventType.DAMAGED_PLAYER) { - return false; - } Player controller = game.getPlayer(getControllerId()); if (controller == null) { return false; } - Permanent attacker = game.getPermanentOrLKIBattlefield(event.getSourceId()); - if (attacker == null) { + DamagedBatchForOnePlayerEvent dEvent = (DamagedBatchForOnePlayerEvent) event; + + if (!controller.hasOpponent(dEvent.getTargetId(), game)){ return false; } - if (((DamagedPlayerEvent) event).isCombatDamage() - && controller.hasOpponent(event.getTargetId(), game) - && attacker.hasSubtype(SubType.WARRIOR, game) - && !usedForCombatDamageStep) { - usedForCombatDamageStep = true; - return true; + if (!dEvent.isCombatDamage()) { + return false; } - return false; + + int warriorDamage = dEvent.getEvents() + .stream() + .filter(ev -> { + Permanent attacker = game.getPermanentOrLKIBattlefield(ev.getSourceId()); + return attacker != null && attacker.hasSubtype(SubType.WARRIOR, game); + }) + .mapToInt(GameEvent::getAmount) + .sum(); + + return warriorDamage > 0; } @Override diff --git a/Mage.Sets/src/mage/cards/m/MineRaider.java b/Mage.Sets/src/mage/cards/m/MineRaider.java new file mode 100644 index 00000000000..0213b6351dc --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MineRaider.java @@ -0,0 +1,65 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.hint.ConditionHint; +import mage.abilities.hint.Hint; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.filter.predicate.mageobject.OutlawPredicate; +import mage.game.permanent.token.TreasureToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MineRaider extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledPermanent(); + + static { + filter.add(AnotherPredicate.instance); + filter.add(OutlawPredicate.instance); + } + + private static final Condition condition = new PermanentsOnTheBattlefieldCondition(filter); + private static final Hint hint = new ConditionHint(condition, "You control another outlaw"); + + public MineRaider(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // When Mine Raider enters the battlefield, if you control another outlaw, create a Treasure token. + this.addAbility(new ConditionalInterveningIfTriggeredAbility( + new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new TreasureToken())), condition, + "When {this} enters the battlefield, if you control another outlaw, create a Treasure token." + ).addHint(hint)); + } + + private MineRaider(final MineRaider card) { + super(card); + } + + @Override + public MineRaider copy() { + return new MineRaider(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MirageMesa.java b/Mage.Sets/src/mage/cards/m/MirageMesa.java new file mode 100644 index 00000000000..f69d9440f84 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MirageMesa.java @@ -0,0 +1,40 @@ +package mage.cards.m; + +import mage.abilities.common.EntersBattlefieldTappedAsItEntersChooseColorAbility; +import mage.abilities.costs.common.TapSourceCost; +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.SubType; +import mage.constants.Zone; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MirageMesa extends CardImpl { + + public MirageMesa(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + this.subtype.add(SubType.DESERT); + + // Mirage Mesa enters the battlefield tapped. As it enters, choose a color. + this.addAbility(new EntersBattlefieldTappedAsItEntersChooseColorAbility()); + + // {T}: Add one mana of the chosen color. + this.addAbility(new SimpleManaAbility(Zone.BATTLEFIELD, new AddManaChosenColorEffect(), new TapSourceCost())); + } + + private MirageMesa(final MirageMesa card) { + super(card); + } + + @Override + public MirageMesa copy() { + return new MirageMesa(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MiriamHerdWhisperer.java b/Mage.Sets/src/mage/cards/m/MiriamHerdWhisperer.java new file mode 100644 index 00000000000..55a388ae9f8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MiriamHerdWhisperer.java @@ -0,0 +1,69 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.common.AttacksCreatureYouControlTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.MyTurnCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.common.continuous.GainAbilityAllEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +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.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.Predicates; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MiriamHerdWhisperer extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledPermanent("a Mount or Vehicle you control"); + + static { + filter.add(Predicates.or( + SubType.MOUNT.getPredicate(), + SubType.VEHICLE.getPredicate() + )); + } + + public MiriamHerdWhisperer(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.DRUID); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // As long as it's your turn, Mounts and Vehicles you control have hexproof. + this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( + new GainAbilityAllEffect(HexproofAbility.getInstance(), Duration.WhileOnBattlefield, filter), + MyTurnCondition.instance, "as long as it's your turn, Mounts and Vehicles you control have hexproof" + ))); + + // Whenever a Mount or Vehicle you control attacks, put a +1/+1 counter on it. + this.addAbility(new AttacksCreatureYouControlTriggeredAbility( + new AddCountersTargetEffect(CounterType.P1P1.createInstance()) + .setText("put a +1/+1 counter on it"), + false, filter, true + )); + } + + private MiriamHerdWhisperer(final MiriamHerdWhisperer card) { + super(card); + } + + @Override + public MiriamHerdWhisperer copy() { + return new MiriamHerdWhisperer(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MissionBriefing.java b/Mage.Sets/src/mage/cards/m/MissionBriefing.java index be75e483896..02779e71b0e 100644 --- a/Mage.Sets/src/mage/cards/m/MissionBriefing.java +++ b/Mage.Sets/src/mage/cards/m/MissionBriefing.java @@ -2,7 +2,7 @@ package mage.cards.m; import mage.abilities.Ability; import mage.abilities.effects.Effect; -import mage.abilities.effects.common.MayCastTargetThenExileEffect; +import mage.abilities.effects.common.MayCastTargetCardEffect; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.replacement.ThatSpellGraveyardExileReplacementEffect; import mage.cards.CardImpl; @@ -68,7 +68,7 @@ class MissionBriefingEffect extends OneShotEffect { player.surveil(2, source, game); Target target = new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY_FROM_YOUR_GRAVEYARD); player.choose(outcome, target, source, game); - Effect effect = new MayCastTargetThenExileEffect(Duration.EndOfTurn); + Effect effect = new MayCastTargetCardEffect(Duration.EndOfTurn, true); effect.setTargetPointer(new FixedTarget(target.getFirstTarget(), game)); effect.apply(game, source); return true; diff --git a/Mage.Sets/src/mage/cards/m/MobileHomestead.java b/Mage.Sets/src/mage/cards/m/MobileHomestead.java new file mode 100644 index 00000000000..18ab876c226 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MobileHomestead.java @@ -0,0 +1,101 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +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.OneShotEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.keyword.CrewAbility; +import mage.abilities.keyword.HasteAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.CardsImpl; +import mage.constants.*; +import mage.filter.common.FilterControlledPermanent; +import mage.game.Game; +import mage.players.Player; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class MobileHomestead extends CardImpl { + + private static final Condition condition = new PermanentsOnTheBattlefieldCondition(new FilterControlledPermanent(SubType.MOUNT)); + + public MobileHomestead(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); + + this.subtype.add(SubType.VEHICLE); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Mobile Homestead has haste as long as you control a Mount. + this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( + new GainAbilitySourceEffect(HasteAbility.getInstance(), Duration.WhileOnBattlefield), + condition, "{this} has haste as long as you control a Mount" + ))); + + // Whenever Mobile Homestead attacks, look at the top card of your library. If it's a land card, you may put it onto the battlefield tapped. + this.addAbility(new AttacksTriggeredAbility(new MobileHomesteadEffect())); + + // Crew 2 + this.addAbility(new CrewAbility(2)); + } + + private MobileHomestead(final MobileHomestead card) { + super(card); + } + + @Override + public MobileHomestead copy() { + return new MobileHomestead(this); + } +} + +class MobileHomesteadEffect extends OneShotEffect { + + MobileHomesteadEffect() { + super(Outcome.Benefit); + staticText = "look at the top card of your library. " + + "If it's a land card, you may put it onto the battlefield tapped"; + } + + private MobileHomesteadEffect(final MobileHomesteadEffect effect) { + super(effect); + } + + @Override + public MobileHomesteadEffect copy() { + return new MobileHomesteadEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + MageObject sourceObject = source.getSourceObject(game); + if (controller == null || sourceObject == null) { + return false; + } + Card card = controller.getLibrary().getFromTop(game); + if (card == null) { + return true; + } + controller.lookAtCards(sourceObject.getIdName(), new CardsImpl(card), game); + if (!card.isLand(game)) { + return true; + } + String message = "Put " + card.getLogName() + " onto the battlefield tapped?"; + if (controller.chooseUse(Outcome.PutLandInPlay, message, source, game)) { + controller.moveCards(card, Zone.BATTLEFIELD, source, game, true, false, false, null); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/m/MoltenDuplication.java b/Mage.Sets/src/mage/cards/m/MoltenDuplication.java new file mode 100644 index 00000000000..b2025ac58e4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MoltenDuplication.java @@ -0,0 +1,73 @@ +package mage.cards.m; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenCopyTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.HasteAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.target.TargetPermanent; +import mage.target.targetpointer.FixedTargets; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MoltenDuplication extends CardImpl { + + public MoltenDuplication(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{R}"); + + // Create a token that's a copy of target artifact or creature you control, except it's an artifact in addition to its other types. It gains haste until end of turn. Sacrifice it at the beginning of the next end step. + this.getSpellAbility().addEffect(new MoltenDuplicationEffect()); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACT_OR_CREATURE)); + } + + private MoltenDuplication(final MoltenDuplication card) { + super(card); + } + + @Override + public MoltenDuplication copy() { + return new MoltenDuplication(this); + } +} + +class MoltenDuplicationEffect extends OneShotEffect { + + MoltenDuplicationEffect() { + super(Outcome.Benefit); + staticText = "create a token that's a copy of target artifact or creature you control, " + + "except it's an artifact in addition to its other types. " + + "It gains haste until end of turn. Sacrifice it at the beginning of the next end step"; + } + + private MoltenDuplicationEffect(final MoltenDuplicationEffect effect) { + super(effect); + } + + @Override + public MoltenDuplicationEffect copy() { + return new MoltenDuplicationEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + CreateTokenCopyTargetEffect effect = new CreateTokenCopyTargetEffect( + null, CardType.ARTIFACT, false + ); + effect.apply(game, source); + game.addEffect(new GainAbilityTargetEffect( + HasteAbility.getInstance(), Duration.EndOfTurn + ).setTargetPointer(new FixedTargets(effect.getAddedPermanents(), game)), source); + effect.sacrificeTokensCreatedAtNextEndStep(game, source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/m/MoltenPrimordial.java b/Mage.Sets/src/mage/cards/m/MoltenPrimordial.java index dbd48ca315a..026ec4f17b7 100644 --- a/Mage.Sets/src/mage/cards/m/MoltenPrimordial.java +++ b/Mage.Sets/src/mage/cards/m/MoltenPrimordial.java @@ -15,14 +15,11 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Outcome; import mage.constants.SubType; -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.Target; import mage.target.common.TargetCreaturePermanent; -import mage.target.targetadjustment.TargetAdjuster; +import mage.target.targetadjustment.EachOpponentPermanentTargetsAdjuster; import mage.target.targetpointer.FixedTarget; import java.util.UUID; @@ -44,7 +41,7 @@ public final class MoltenPrimordial extends CardImpl { // When Molten Primordial enters the battlefield, for each opponent, take control of up to one target creature that player controls until end of turn. Untap those creatures. They have haste until end of turn. Ability ability = new EntersBattlefieldTriggeredAbility(new MoltenPrimordialEffect(), false); - ability.setTargetAdjuster(MoltenPrimordialAdjuster.instance); + ability.setTargetAdjuster(new EachOpponentPermanentTargetsAdjuster(new TargetCreaturePermanent(0,1))); this.addAbility(ability); } @@ -58,24 +55,6 @@ public final class MoltenPrimordial extends CardImpl { } } -enum MoltenPrimordialAdjuster implements TargetAdjuster { - instance; - - @Override - public void adjustTargets(Ability ability, Game game) { - ability.getTargets().clear(); - for (UUID opponentId : game.getOpponents(ability.getControllerId())) { - Player opponent = game.getPlayer(opponentId); - if (opponent != null) { - FilterCreaturePermanent filter = new FilterCreaturePermanent("creature from opponent " + opponent.getLogName()); - filter.add(new ControllerIdPredicate(opponentId)); - TargetCreaturePermanent target = new TargetCreaturePermanent(0, 1, filter, false); - ability.addTarget(target); - } - } - } -} - class MoltenPrimordialEffect extends OneShotEffect { MoltenPrimordialEffect() { diff --git a/Mage.Sets/src/mage/cards/m/MonoxaMidwayManager.java b/Mage.Sets/src/mage/cards/m/MonoxaMidwayManager.java index 84cb014162e..e02fb8d6bd4 100644 --- a/Mage.Sets/src/mage/cards/m/MonoxaMidwayManager.java +++ b/Mage.Sets/src/mage/cards/m/MonoxaMidwayManager.java @@ -77,7 +77,7 @@ class MonoxaMidwayManagerTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { int result = ((DieRolledEvent) event).getResult(); - if (!isControlledBy(event.getPlayerId()) || result < 3) { + if (!isControlledBy(event.getTargetId()) || result < 3) { return false; } this.getEffects().setValue("dieRoll", result); diff --git a/Mage.Sets/src/mage/cards/m/MournersSurprise.java b/Mage.Sets/src/mage/cards/m/MournersSurprise.java new file mode 100644 index 00000000000..958e3be8029 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MournersSurprise.java @@ -0,0 +1,36 @@ +package mage.cards.m; + +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; +import mage.game.permanent.token.MercenaryToken; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MournersSurprise extends CardImpl { + + public MournersSurprise(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{B}"); + + // Return up to one target creature card from your graveyard to your hand. Create a 1/1 red Mercenary creature token with "{T}: Target creature you control gets +1/+0 until end of turn. Activate only as a sorcery." + this.getSpellAbility().addEffect(new ReturnFromGraveyardToHandTargetEffect()); + this.getSpellAbility().addEffect(new CreateTokenEffect(new MercenaryToken())); + this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(0, 1, StaticFilters.FILTER_CARD_CREATURE_YOUR_GRAVEYARD)); + } + + private MournersSurprise(final MournersSurprise card) { + super(card); + } + + @Override + public MournersSurprise copy() { + return new MournersSurprise(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MrHousePresidentAndCEO.java b/Mage.Sets/src/mage/cards/m/MrHousePresidentAndCEO.java index 8943464571b..067d01439e5 100644 --- a/Mage.Sets/src/mage/cards/m/MrHousePresidentAndCEO.java +++ b/Mage.Sets/src/mage/cards/m/MrHousePresidentAndCEO.java @@ -77,7 +77,7 @@ class MrHousePresidentAndCEOTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { DieRolledEvent drEvent = (DieRolledEvent) event; - if (this.isControlledBy(event.getPlayerId()) && drEvent.getRollDieType() == RollDieType.NUMERICAL) { + if (this.isControlledBy(event.getTargetId()) && drEvent.getRollDieType() == RollDieType.NUMERICAL) { // looks for "result" instead "natural result" int result = drEvent.getResult(); this.getEffects().setValue("rolled", result); diff --git a/Mage.Sets/src/mage/cards/m/MysticForge.java b/Mage.Sets/src/mage/cards/m/MysticForge.java index 158399d614a..bb3f3c3b2fa 100644 --- a/Mage.Sets/src/mage/cards/m/MysticForge.java +++ b/Mage.Sets/src/mage/cards/m/MysticForge.java @@ -7,7 +7,7 @@ import mage.abilities.costs.common.PayLifeCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.effects.common.ExileCardsFromTopOfLibraryControllerEffect; import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; -import mage.abilities.effects.common.continuous.PlayTheTopCardEffect; +import mage.abilities.effects.common.continuous.PlayFromTopOfLibraryEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -40,7 +40,7 @@ public final class MysticForge extends CardImpl { this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); // You may cast artifact spells and colorless spells from the top of your library. - this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(TargetController.YOU, filter, false))); + this.addAbility(new SimpleStaticAbility(new PlayFromTopOfLibraryEffect(filter))); // {T}, Pay 1 life: Exile the top card of your library. Ability ability = new SimpleActivatedAbility(new ExileCardsFromTopOfLibraryControllerEffect(1), new TapSourceCost()); diff --git a/Mage.Sets/src/mage/cards/m/MysticalTether.java b/Mage.Sets/src/mage/cards/m/MysticalTether.java new file mode 100644 index 00000000000..e1af5168013 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MysticalTether.java @@ -0,0 +1,41 @@ +package mage.cards.m; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.PayMoreToCastAsThoughtItHadFlashAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.ExileUntilSourceLeavesEffect; +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 MysticalTether extends CardImpl { + + public MysticalTether(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{W}"); + + // You may cast Mystical Tether as though it had flash if you pay {2} more to cast it. + this.addAbility(new PayMoreToCastAsThoughtItHadFlashAbility(this, new ManaCostsImpl<>("{2}")).setRuleAtTheTop(true)); + + // When Mystical Tether enters the battlefield, exile target artifact or creature an opponent controls until Mystical Tether leaves the battlefield. + Ability ability = new EntersBattlefieldTriggeredAbility(new ExileUntilSourceLeavesEffect()); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_OPPONENTS_PERMANENT_ARTIFACT_OR_CREATURE)); + this.addAbility(ability); + } + + private MysticalTether(final MysticalTether card) { + super(card); + } + + @Override + public MysticalTether copy() { + return new MysticalTether(this); + } +} diff --git a/Mage.Sets/src/mage/cards/n/NaliaDeArnise.java b/Mage.Sets/src/mage/cards/n/NaliaDeArnise.java index 72195adfafd..ab9c8e933e1 100644 --- a/Mage.Sets/src/mage/cards/n/NaliaDeArnise.java +++ b/Mage.Sets/src/mage/cards/n/NaliaDeArnise.java @@ -8,7 +8,7 @@ import mage.abilities.condition.common.FullPartyCondition; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.common.continuous.GainAbilityAllEffect; import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; -import mage.abilities.effects.common.continuous.PlayTheTopCardEffect; +import mage.abilities.effects.common.continuous.PlayFromTopOfLibraryEffect; import mage.abilities.effects.common.counter.AddCountersAllEffect; import mage.abilities.hint.common.PartyCountHint; import mage.abilities.keyword.DeathtouchAbility; @@ -51,9 +51,7 @@ public final class NaliaDeArnise extends CardImpl { this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); // You may cast Cleric, Rogue, Warrior, and Wizard spells from the top of your library. - this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect( - TargetController.YOU, filter, false - ))); + this.addAbility(new SimpleStaticAbility(new PlayFromTopOfLibraryEffect(filter))); // At the beginning of combat on your turn, if you have a full party, put a +1/+1 counter on each creature you control and those creatures gain deathtouch until end of turn. Ability ability = new ConditionalInterveningIfTriggeredAbility( diff --git a/Mage.Sets/src/mage/cards/n/NetheresePuzzleWard.java b/Mage.Sets/src/mage/cards/n/NetheresePuzzleWard.java index 92f35f4be5d..e51a969093b 100644 --- a/Mage.Sets/src/mage/cards/n/NetheresePuzzleWard.java +++ b/Mage.Sets/src/mage/cards/n/NetheresePuzzleWard.java @@ -91,7 +91,7 @@ class NetheresePuzzleWardTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { DieRolledEvent drEvent = (DieRolledEvent) event; - return isControlledBy(drEvent.getPlayerId()) + return isControlledBy(drEvent.getTargetId()) && drEvent.getNaturalResult() == drEvent.getSides(); } diff --git a/Mage.Sets/src/mage/cards/n/NeutralizeTheGuards.java b/Mage.Sets/src/mage/cards/n/NeutralizeTheGuards.java new file mode 100644 index 00000000000..bb4e17b9fa6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/NeutralizeTheGuards.java @@ -0,0 +1,68 @@ +package mage.cards.n; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.AddContinuousEffectToGame; +import mage.abilities.effects.common.continuous.BoostAllEffect; +import mage.abilities.effects.keyword.SurveilEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.ControllerIdPredicate; +import mage.game.Game; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class NeutralizeTheGuards extends CardImpl { + + public NeutralizeTheGuards(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{B}"); + + // Creatures target opponent controls get -1/-1 until end of turn. Surveil 2. + this.getSpellAbility().addEffect(new NeutralizeTheGuardsEffect()); + this.getSpellAbility().addTarget(new TargetOpponent()); + this.getSpellAbility().addEffect(new SurveilEffect(2)); + } + + private NeutralizeTheGuards(final NeutralizeTheGuards card) { + super(card); + } + + @Override + public NeutralizeTheGuards copy() { + return new NeutralizeTheGuards(this); + } +} + +class NeutralizeTheGuardsEffect extends OneShotEffect { + + NeutralizeTheGuardsEffect() { + super(Outcome.Benefit); + staticText = "creatures target opponent controls get -1/-1 until end of turn"; + } + + private NeutralizeTheGuardsEffect(final NeutralizeTheGuardsEffect effect) { + super(effect); + } + + @Override + public NeutralizeTheGuardsEffect copy() { + return new NeutralizeTheGuardsEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + FilterCreaturePermanent filter = new FilterCreaturePermanent(); + filter.add(new ControllerIdPredicate(source.getFirstTarget())); + return new AddContinuousEffectToGame( + new BoostAllEffect(-1, -1, Duration.EndOfTurn, filter, false) + ).apply(game, source); + } +} diff --git a/Mage.Sets/src/mage/cards/n/NexusOfBecoming.java b/Mage.Sets/src/mage/cards/n/NexusOfBecoming.java new file mode 100644 index 00000000000..f7117310349 --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/NexusOfBecoming.java @@ -0,0 +1,88 @@ +package mage.cards.n; + +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfCombatTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenCopyTargetEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +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.PermanentCard; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInHand; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class NexusOfBecoming extends CardImpl { + + public NexusOfBecoming(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{6}"); + + // At the beginning of combat on your turn, draw a card. Then you may exile an artifact or creature card from your hand. If you do, create a token that's a copy of the exiled card, except it's a 3/3 Golem artifact creature in addition to its other types. + Ability ability = new BeginningOfCombatTriggeredAbility( + new DrawCardSourceControllerEffect(1), TargetController.YOU, false + ); + ability.addEffect(new NexusOfBecomingEffect()); + this.addAbility(ability); + } + + private NexusOfBecoming(final NexusOfBecoming card) { + super(card); + } + + @Override + public NexusOfBecoming copy() { + return new NexusOfBecoming(this); + } +} + +class NexusOfBecomingEffect extends OneShotEffect { + + NexusOfBecomingEffect() { + super(Outcome.Benefit); + staticText = "Then you may exile an artifact or creature card from your hand. " + + "If you do, create a token that's a copy of the exiled card, " + + "except it's a 3/3 Golem artifact creature in addition to its other types."; + } + + private NexusOfBecomingEffect(final NexusOfBecomingEffect effect) { + super(effect); + } + + @Override + public NexusOfBecomingEffect copy() { + return new NexusOfBecomingEffect(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_ARTIFACT_OR_CREATURE + ); + player.choose(outcome, player.getHand(), target, source, game); + Card card = game.getCard(target.getFirstTarget()); + if (card == null) { + return false; + } + player.moveCards(card, Zone.EXILED, source, game); + return new CreateTokenCopyTargetEffect( + null, CardType.CREATURE, false, 1, false, + false, null, 3, 3, false + ).setBecomesArtifact(true) + .withAdditionalSubType(SubType.GOLEM) + .setSavedPermanent(new PermanentCard(card, source.getControllerId(), game)) + .apply(game, source); + } +} diff --git a/Mage.Sets/src/mage/cards/n/NezumiLinkbreaker.java b/Mage.Sets/src/mage/cards/n/NezumiLinkbreaker.java new file mode 100644 index 00000000000..17935238fdf --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/NezumiLinkbreaker.java @@ -0,0 +1,39 @@ +package mage.cards.n; + +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.MercenaryToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class NezumiLinkbreaker extends CardImpl { + + public NezumiLinkbreaker(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{B}"); + + this.subtype.add(SubType.RAT); + this.subtype.add(SubType.WARLOCK); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // When Nezumi Linkbreaker dies, create a 1/1 red Mercenary creature token with "{T}: Target creature you control gets +1/+0 until end of turn. Activate only as a sorcery." + this.addAbility(new DiesSourceTriggeredAbility(new CreateTokenEffect(new MercenaryToken()))); + } + + private NezumiLinkbreaker(final NezumiLinkbreaker card) { + super(card); + } + + @Override + public NezumiLinkbreaker copy() { + return new NezumiLinkbreaker(this); + } +} diff --git a/Mage.Sets/src/mage/cards/n/NickValentinePrivateEye.java b/Mage.Sets/src/mage/cards/n/NickValentinePrivateEye.java new file mode 100644 index 00000000000..78189c5ca2e --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/NickValentinePrivateEye.java @@ -0,0 +1,65 @@ +package mage.cards.n; + +import java.util.UUID; + +import mage.MageInt; +import mage.abilities.common.DiesThisOrAnotherCreatureTriggeredAbility; +import mage.abilities.common.SimpleEvasionAbility; +import mage.abilities.effects.common.combat.CantBeBlockedByCreaturesSourceEffect; +import mage.abilities.effects.keyword.InvestigateEffect; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; + +/** + * @author Cguy7777 + */ +public final class NickValentinePrivateEye extends CardImpl { + + private static final FilterCreaturePermanent filterNonArtifact + = new FilterCreaturePermanent("except by artifact creatures"); + private static final FilterControlledCreaturePermanent filterControlledArtifact + = new FilterControlledCreaturePermanent("artifact creature you control"); + + static { + filterNonArtifact.add(Predicates.not(CardType.ARTIFACT.getPredicate())); + filterControlledArtifact.add(CardType.ARTIFACT.getPredicate()); + } + + public NickValentinePrivateEye(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{2}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SYNTH); + this.subtype.add(SubType.DETECTIVE); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Nick Valentine, Private Eye can't be blocked except by artifact creatures. + this.addAbility(new SimpleEvasionAbility( + new CantBeBlockedByCreaturesSourceEffect(filterNonArtifact, Duration.WhileOnBattlefield))); + + // Whenever Nick Valentine or another artifact creature you control dies, you may investigate. + this.addAbility(new DiesThisOrAnotherCreatureTriggeredAbility( + new InvestigateEffect() + .setText("investigate. (To investigate, create a Clue token. " + + "It's an artifact with \"{2}, Sacrifice this artifact: Draw a card.\")"), + true, + filterControlledArtifact)); + } + + private NickValentinePrivateEye(final NickValentinePrivateEye card) { + super(card); + } + + @Override + public NickValentinePrivateEye copy() { + return new NickValentinePrivateEye(this); + } +} diff --git a/Mage.Sets/src/mage/cards/n/NimbleBrigand.java b/Mage.Sets/src/mage/cards/n/NimbleBrigand.java new file mode 100644 index 00000000000..994f8a5187a --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/NimbleBrigand.java @@ -0,0 +1,51 @@ +package mage.cards.n; + +import mage.MageInt; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.CommittedCrimeCondition; +import mage.abilities.decorator.ConditionalRestrictionEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.combat.CantBeBlockedSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.watchers.common.CommittedCrimeWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class NimbleBrigand extends CardImpl { + + public NimbleBrigand(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(1); + this.toughness = new MageInt(3); + + // Nimble Brigand can't be blocked if you've committed a crime this turn. + this.addAbility(new SimpleStaticAbility(new ConditionalRestrictionEffect( + new CantBeBlockedSourceEffect(), CommittedCrimeCondition.instance, + "{this} can't be blocked if you've committed a crime this turn" + )).addHint(CommittedCrimeCondition.getHint()), new CommittedCrimeWatcher()); + + // Whenever Nimble Brigand deals combat damage to a player, draw a card. + this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility( + new DrawCardSourceControllerEffect(1), false + )); + } + + private NimbleBrigand(final NimbleBrigand card) { + super(card); + } + + @Override + public NimbleBrigand copy() { + return new NimbleBrigand(this); + } +} diff --git a/Mage.Sets/src/mage/cards/n/NurturingPixie.java b/Mage.Sets/src/mage/cards/n/NurturingPixie.java new file mode 100644 index 00000000000..2d8ed7c101b --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/NurturingPixie.java @@ -0,0 +1,92 @@ +package mage.cards.n; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterNonlandPermanent; +import mage.filter.predicate.Predicates; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class NurturingPixie extends CardImpl { + + private static final FilterPermanent filter + = new FilterNonlandPermanent("non-Faerie, nonland permanent you control"); + + static { + filter.add(TargetController.YOU.getControllerPredicate()); + filter.add(Predicates.not(SubType.FAERIE.getPredicate())); + } + + public NurturingPixie(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W}"); + + this.subtype.add(SubType.FAERIE); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When Nurturing Pixie enters the battlefield, return up to one target non-Faerie, nonland permanent you control to its owner's hand. If a permanent was returned this way, put a +1/+1 counter on Nurturing Pixie. + Ability ability = new EntersBattlefieldTriggeredAbility(new NurturingPixieEffect()); + ability.addTarget(new TargetPermanent(0, 1, filter)); + this.addAbility(ability); + } + + private NurturingPixie(final NurturingPixie card) { + super(card); + } + + @Override + public NurturingPixie copy() { + return new NurturingPixie(this); + } +} + +class NurturingPixieEffect extends OneShotEffect { + + NurturingPixieEffect() { + super(Outcome.Benefit); + staticText = "return up to one target non-Faerie, nonland permanent you control " + + "to its owner's hand. If a permanent was returned this way, put a +1/+1 counter on {this}"; + } + + private NurturingPixieEffect(final NurturingPixieEffect effect) { + super(effect); + } + + @Override + public NurturingPixieEffect copy() { + return new NurturingPixieEffect(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; + } + player.moveCards(permanent, Zone.HAND, source, game); + Optional.ofNullable(source.getSourcePermanentIfItStillExists(game)) + .ifPresent(p -> p.addCounters(CounterType.P1P1.createInstance(), source, game)); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/o/OasisGardener.java b/Mage.Sets/src/mage/cards/o/OasisGardener.java new file mode 100644 index 00000000000..cbf4b852dbf --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OasisGardener.java @@ -0,0 +1,41 @@ +package mage.cards.o; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.mana.AnyColorManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class OasisGardener extends CardImpl { + + public OasisGardener(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{3}"); + + this.subtype.add(SubType.SCARECROW); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // When Oasis Gardener enters the battlefield, you gain 2 life. + this.addAbility(new EntersBattlefieldTriggeredAbility(new GainLifeEffect(2))); + + // {T}: Add one mana of any color. + this.addAbility(new AnyColorManaAbility()); + } + + private OasisGardener(final OasisGardener card) { + super(card); + } + + @Override + public OasisGardener copy() { + return new OasisGardener(this); + } +} diff --git a/Mage.Sets/src/mage/cards/o/ObNixilisCaptiveKingpin.java b/Mage.Sets/src/mage/cards/o/ObNixilisCaptiveKingpin.java new file mode 100644 index 00000000000..13c3eaff2e5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/ObNixilisCaptiveKingpin.java @@ -0,0 +1,105 @@ +package mage.cards.o; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.ExileTopXMayPlayUntilEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.constants.*; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.events.*; +import mage.util.CardUtil; + +/** + * + * @author jimga150 + */ +public final class ObNixilisCaptiveKingpin extends CardImpl { + + public ObNixilisCaptiveKingpin(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.DEMON); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Whenever one or more opponents each lose exactly 1 life, put a +1/+1 counter on Ob Nixilis, Captive Kingpin. Exile the top card of your library. Until your next end step, you may play that card. + Ability ability = new ObNixilisCaptiveKingpinAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()) + ); + ability.addEffect(new ExileTopXMayPlayUntilEffect(1, Duration.UntilYourNextEndStep) + .withTextOptions("that card", false)); + + this.addAbility(ability); + + } + + private ObNixilisCaptiveKingpin(final ObNixilisCaptiveKingpin card) { + super(card); + } + + @Override + public ObNixilisCaptiveKingpin copy() { + return new ObNixilisCaptiveKingpin(this); + } +} + +class ObNixilisCaptiveKingpinAbility extends TriggeredAbilityImpl { + + ObNixilisCaptiveKingpinAbility(Effect effect) { + super(Zone.BATTLEFIELD, effect); + setTriggerPhrase("Whenever one or more opponents each lose exactly 1 life, "); + } + + private ObNixilisCaptiveKingpinAbility(final ObNixilisCaptiveKingpinAbility ability) { + super(ability); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.LOST_LIFE_BATCH; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + + LifeLostBatchEvent lifeLostBatchEvent = (LifeLostBatchEvent) event; + + boolean opponentLostLife = false; + boolean allis1 = true; + + for (UUID targetPlayer : CardUtil.getEventTargets(lifeLostBatchEvent)) { + // skip controller + if (targetPlayer.equals(getControllerId())) { + continue; + } + opponentLostLife = true; + + int lifeLost = lifeLostBatchEvent.getLifeLostByPlayer(targetPlayer); + if (lifeLost != 1) { + allis1 = false; + break; + } + } + return opponentLostLife && allis1; + } + + @Override + public ObNixilisCaptiveKingpinAbility copy() { + return new ObNixilisCaptiveKingpinAbility(this); + } +} diff --git a/Mage.Sets/src/mage/cards/o/ObekaSplitterOfSeconds.java b/Mage.Sets/src/mage/cards/o/ObekaSplitterOfSeconds.java new file mode 100644 index 00000000000..09f191a0bca --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/ObekaSplitterOfSeconds.java @@ -0,0 +1,84 @@ +package mage.cards.o; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.dynamicvalue.common.SavedDamageValue; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.game.Game; +import mage.game.turn.TurnMod; +import mage.game.turn.UpkeepStep; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class ObekaSplitterOfSeconds extends CardImpl { + + public ObekaSplitterOfSeconds(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}{B}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.OGRE); + this.subtype.add(SubType.WARLOCK); + this.power = new MageInt(2); + this.toughness = new MageInt(5); + + // Menace + this.addAbility(new MenaceAbility()); + + // Whenever Obeka, Splitter of Seconds deals combat damage to a player, you get that many additional upkeep steps after this phase. + this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility( + new ObekaSplitterOfSecondsEffect(), + false, true + )); + + } + + private ObekaSplitterOfSeconds(final ObekaSplitterOfSeconds card) { + super(card); + } + + @Override + public ObekaSplitterOfSeconds copy() { + return new ObekaSplitterOfSeconds(this); + } +} + +class ObekaSplitterOfSecondsEffect extends OneShotEffect { + + ObekaSplitterOfSecondsEffect() { + super(Outcome.Neutral); + staticText = "you get that many additional upkeep steps after this phase"; + } + + private ObekaSplitterOfSecondsEffect(final ObekaSplitterOfSecondsEffect effect) { + super(effect); + } + + @Override + public ObekaSplitterOfSecondsEffect copy() { + return new ObekaSplitterOfSecondsEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + int extraSteps = SavedDamageValue.MANY.calculate(game, source, this); + if (extraSteps <= 0) { + return false; + } + for (int i = 0; i < extraSteps; ++i) { + game.getState().getTurnMods().add(new TurnMod(source.getControllerId()).withExtraStep(new UpkeepStep())); + } + return true; + } + +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/o/ObscuringAether.java b/Mage.Sets/src/mage/cards/o/ObscuringAether.java index 294312f385b..1cf0f4c7792 100644 --- a/Mage.Sets/src/mage/cards/o/ObscuringAether.java +++ b/Mage.Sets/src/mage/cards/o/ObscuringAether.java @@ -5,13 +5,12 @@ import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.Effect; import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect; -import mage.abilities.effects.common.cost.MorphSpellsCostReductionControllerEffect; +import mage.abilities.effects.common.cost.FaceDownSpellsCostReductionControllerEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Zone; -import mage.filter.common.FilterCreatureCard; import java.util.UUID; @@ -24,7 +23,7 @@ public final class ObscuringAether extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{G}"); // Face-down creature spells you cast cost {1} less to cast. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new MorphSpellsCostReductionControllerEffect(1))); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new FaceDownSpellsCostReductionControllerEffect(1))); // {1}{G}: Turn Obscuring Aether face down. Effect effect = new BecomesFaceDownCreatureEffect(Duration.Custom, BecomesFaceDownCreatureEffect.FaceDownType.MANUAL); diff --git a/Mage.Sets/src/mage/cards/o/OjerAxonilDeepestMight.java b/Mage.Sets/src/mage/cards/o/OjerAxonilDeepestMight.java index d17fe96fa19..9700e7368a6 100644 --- a/Mage.Sets/src/mage/cards/o/OjerAxonilDeepestMight.java +++ b/Mage.Sets/src/mage/cards/o/OjerAxonilDeepestMight.java @@ -13,6 +13,7 @@ import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; +import mage.game.Controllable; import mage.game.Game; import mage.game.events.DamageEvent; import mage.game.events.GameEvent; @@ -132,9 +133,19 @@ class OjerAxonilDeepestMightReplacementEffect extends ReplacementEffectImpl { Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(event.getSourceId()); if (sourcePermanent == null) { sourceObject = game.getObject(event.getSourceId()); + if (sourceObject == null + || !(sourceObject instanceof Controllable) + || !((Controllable) sourceObject).isControlledBy(controller.getId()) + ) { + return false; // Only source you control. + } } else { + if (!sourcePermanent.isControlledBy(controller.getId())) { + return false; // Only source you control. + } sourceObject = sourcePermanent; } + Permanent ojer = source.getSourcePermanentIfItStillExists(game); DamageEvent dmgEvent = (DamageEvent) event; diff --git a/Mage.Sets/src/mage/cards/o/OkoTheRingleader.java b/Mage.Sets/src/mage/cards/o/OkoTheRingleader.java new file mode 100644 index 00000000000..d7073ece09b --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OkoTheRingleader.java @@ -0,0 +1,142 @@ +package mage.cards.o; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.LoyaltyAbility; +import mage.abilities.common.BeginningOfCombatTriggeredAbility; +import mage.abilities.condition.common.CommittedCrimeCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenCopyTargetEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.discard.DiscardControllerEffect; +import mage.abilities.keyword.HexproofAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterNonlandPermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.ElkToken; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.util.functions.CopyApplier; +import mage.watchers.common.CommittedCrimeWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class OkoTheRingleader extends CardImpl { + + public OkoTheRingleader(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{2}{G}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.OKO); + this.setStartingLoyalty(3); + + // At the beginning of combat on your turn, Oko, the Ringleader becomes a copy of up to one target creature you control until end of turn, except he has hexproof. + Ability ability = new BeginningOfCombatTriggeredAbility( + new OkoTheRingleaderCopySelfEffect(), TargetController.YOU, false + ); + ability.addTarget(new TargetControlledCreaturePermanent(0, 1)); + this.addAbility(ability); + + // +1: Draw two cards. If you've committed a crime this turn, discard a card. Otherwise, discard two cards. + ability = new LoyaltyAbility(new DrawCardSourceControllerEffect(2), 1); + ability.addEffect(new ConditionalOneShotEffect( + new DiscardControllerEffect(1), new DiscardControllerEffect(2), + CommittedCrimeCondition.instance, "if you've committed a crime this turn, " + + "discard a card. Otherwise, discard two cards" + )); + this.addAbility(ability.addHint(CommittedCrimeCondition.getHint()), new CommittedCrimeWatcher()); + + // -1: Create a 3/3 green Elk creature token. + this.addAbility(new LoyaltyAbility(new CreateTokenEffect(new ElkToken()), -1)); + + // -5: For each other nonland permanent you control, create a token that's a copy of that permanent. + this.addAbility(new LoyaltyAbility(new OkoTheRingleaderCopyTokenEffect(), -5)); + } + + private OkoTheRingleader(final OkoTheRingleader card) { + super(card); + } + + @Override + public OkoTheRingleader copy() { + return new OkoTheRingleader(this); + } +} + +class OkoTheRingleaderCopySelfEffect extends OneShotEffect { + + private static final CopyApplier applier = new CopyApplier() { + @Override + public boolean apply(Game game, MageObject blueprint, Ability source, UUID targetObjectId) { + blueprint.getAbilities().add(HexproofAbility.getInstance()); + return true; + } + }; + + OkoTheRingleaderCopySelfEffect() { + super(Outcome.Benefit); + staticText = "{this} becomes a copy of up to one target creature " + + "you control until end of turn, except he has hexproof"; + } + + private OkoTheRingleaderCopySelfEffect(final OkoTheRingleaderCopySelfEffect effect) { + super(effect); + } + + @Override + public OkoTheRingleaderCopySelfEffect copy() { + return new OkoTheRingleaderCopySelfEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + Permanent creature = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent == null || creature == null) { + return false; + } + game.copyPermanent(Duration.EndOfTurn, creature, permanent.getId(), source, applier); + return true; + } +} + +class OkoTheRingleaderCopyTokenEffect extends OneShotEffect { + + private static final FilterPermanent filter = new FilterNonlandPermanent(); + + static { + filter.add(AnotherPredicate.instance); + filter.add(TargetController.YOU.getControllerPredicate()); + } + + OkoTheRingleaderCopyTokenEffect() { + super(Outcome.Benefit); + staticText = "for each other nonland permanent you control, create a token that's a copy of that permanent"; + } + + private OkoTheRingleaderCopyTokenEffect(final OkoTheRingleaderCopyTokenEffect effect) { + super(effect); + } + + @Override + public OkoTheRingleaderCopyTokenEffect copy() { + return new OkoTheRingleaderCopyTokenEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source, game)) { + new CreateTokenCopyTargetEffect().setSavedPermanent(permanent).apply(game, source); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/o/OliviasAttendants.java b/Mage.Sets/src/mage/cards/o/OliviasAttendants.java index 71fcee001dd..294986114fb 100644 --- a/Mage.Sets/src/mage/cards/o/OliviasAttendants.java +++ b/Mage.Sets/src/mage/cards/o/OliviasAttendants.java @@ -14,7 +14,7 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; import mage.game.Game; -import mage.game.events.DamagedBatchEvent; +import mage.game.events.DamagedBatchAllEvent; import mage.game.events.GameEvent; import mage.game.permanent.token.BloodToken; import mage.target.common.TargetAnyTarget; @@ -72,13 +72,12 @@ class OliviasAttendantsTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_PLAYERS - || event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_PERMANENTS; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ALL; } @Override public boolean checkTrigger(GameEvent event, Game game) { - int amount = ((DamagedBatchEvent) event) + int amount = ((DamagedBatchAllEvent) event) .getEvents() .stream() .filter(e -> e.getAttackerId().equals(this.getSourceId())) diff --git a/Mage.Sets/src/mage/cards/o/OltecMatterweaver.java b/Mage.Sets/src/mage/cards/o/OltecMatterweaver.java new file mode 100644 index 00000000000..47958248bc9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OltecMatterweaver.java @@ -0,0 +1,60 @@ +package mage.cards.o; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.common.CreateTokenCopyTargetEffect; +import mage.abilities.effects.common.CreateTokenEffect; +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.FilterControlledArtifactPermanent; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.game.permanent.token.GnomeToken; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class OltecMatterweaver extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledArtifactPermanent("artifact token you control"); + + static { + filter.add(TokenPredicate.TRUE); + } + + public OltecMatterweaver(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ARTIFICER); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // Whenever you cast a creature spell, choose one -- + // * Create a 1/1 colorless Gnome artifact creature token. + Ability ability = new SpellCastControllerTriggeredAbility( + new CreateTokenEffect(new GnomeToken()), StaticFilters.FILTER_SPELL_A_CREATURE, false + ); + + // * Create a token that's a copy of target artifact token you control. + ability.addMode(new Mode(new CreateTokenCopyTargetEffect()).addTarget(new TargetPermanent(filter))); + this.addAbility(ability); + } + + private OltecMatterweaver(final OltecMatterweaver card) { + super(card); + } + + @Override + public OltecMatterweaver copy() { + return new OltecMatterweaver(this); + } +} diff --git a/Mage.Sets/src/mage/cards/o/OmenpathJourney.java b/Mage.Sets/src/mage/cards/o/OmenpathJourney.java new file mode 100644 index 00000000000..cd0419fd783 --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OmenpathJourney.java @@ -0,0 +1,124 @@ +package mage.cards.o; + +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfEndStepTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.game.ExileZone; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCardInLibrary; +import mage.target.common.TargetCardWithDifferentNameInLibrary; +import mage.util.CardUtil; + +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * @author TheElk801 + */ +public final class OmenpathJourney extends CardImpl { + + public OmenpathJourney(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{G}"); + + // When Omenpath Journey enters the battlefield, search your library for up to five land cards that have different names, exile them, then shuffle. + this.addAbility(new EntersBattlefieldTriggeredAbility(new OmenpathJourneySearchEffect())); + + // At the beginning of your end step, choose a card at random exiled with Omenpath Journey and put it onto the battlefield tapped. + this.addAbility(new BeginningOfEndStepTriggeredAbility( + new OmenpathJourneyChooseEffect(), TargetController.YOU, false + )); + } + + private OmenpathJourney(final OmenpathJourney card) { + super(card); + } + + @Override + public OmenpathJourney copy() { + return new OmenpathJourney(this); + } +} + +class OmenpathJourneySearchEffect extends OneShotEffect { + + OmenpathJourneySearchEffect() { + super(Outcome.Benefit); + staticText = "search your library for up to five land cards that have different names, exile them, then shuffle"; + } + + private OmenpathJourneySearchEffect(final OmenpathJourneySearchEffect effect) { + super(effect); + } + + @Override + public OmenpathJourneySearchEffect copy() { + return new OmenpathJourneySearchEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + TargetCardInLibrary target = new TargetCardWithDifferentNameInLibrary( + 0, 5, StaticFilters.FILTER_CARD_LAND + ); + player.searchLibrary(target, source, game); + Set cards = target + .getTargets() + .stream() + .map(uuid -> player.getLibrary().getCard(uuid, game)) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + player.moveCardsToExile( + cards, source, game, true, + CardUtil.getExileZoneId(game, source), + CardUtil.getSourceName(game, source) + ); + player.shuffleLibrary(source, game); + return true; + } +} + +class OmenpathJourneyChooseEffect extends OneShotEffect { + + OmenpathJourneyChooseEffect() { + super(Outcome.Benefit); + staticText = "choose a card at random exiled with {this} and put it onto the battlefield tapped"; + } + + private OmenpathJourneyChooseEffect(final OmenpathJourneyChooseEffect effect) { + super(effect); + } + + @Override + public OmenpathJourneyChooseEffect copy() { + return new OmenpathJourneyChooseEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + ExileZone exileZone = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source)); + return player != null + && exileZone != null + && !exileZone.isEmpty() + && player.moveCards( + exileZone.getRandom(game), Zone.BATTLEFIELD, source, game, + true, false, false, null + ); + } +} diff --git a/Mage.Sets/src/mage/cards/o/OmenportVigilante.java b/Mage.Sets/src/mage/cards/o/OmenportVigilante.java new file mode 100644 index 00000000000..18194f098f8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OmenportVigilante.java @@ -0,0 +1,46 @@ +package mage.cards.o; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.CommittedCrimeCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +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.watchers.common.CommittedCrimeWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class OmenportVigilante extends CardImpl { + + public OmenportVigilante(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.MERCENARY); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Omenport Vigilante has double strike as long as you've committed a crime this turn. + this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( + new GainAbilitySourceEffect(DoubleStrikeAbility.getInstance(), Duration.WhileOnBattlefield), + CommittedCrimeCondition.instance, "{this} has double strike as long as you've committed a crime this turn" + )).addHint(CommittedCrimeCondition.getHint()), new CommittedCrimeWatcher()); + } + + private OmenportVigilante(final OmenportVigilante card) { + super(card); + } + + @Override + public OmenportVigilante copy() { + return new OmenportVigilante(this); + } +} diff --git a/Mage.Sets/src/mage/cards/o/OneLastJob.java b/Mage.Sets/src/mage/cards/o/OneLastJob.java new file mode 100644 index 00000000000..7bb09834455 --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OneLastJob.java @@ -0,0 +1,143 @@ +package mage.cards.o; + +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.abilities.keyword.SpreeAbility; +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.Zone; +import mage.filter.FilterCard; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.permanent.CanBeEnchantedByPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.Target; +import mage.target.TargetPermanent; +import mage.target.common.TargetCardInYourGraveyard; +import mage.util.CardUtil; +import mage.util.SubTypes; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class OneLastJob extends CardImpl { + + private static final FilterCard filter = new FilterCard("Mount or Vehicle card from your graveyard"); + private static final FilterCard filter2 = new FilterCard("Aura or Equipment card"); + + static { + filter.add(Predicates.or(SubType.MOUNT.getPredicate(), SubType.VEHICLE.getPredicate())); + filter2.add(Predicates.or(SubType.AURA.getPredicate(), SubType.EQUIPMENT.getPredicate())); + } + + public OneLastJob(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{W}"); + + // Spree + this.addAbility(new SpreeAbility(this)); + + // + {2} -- Return target creature card from your graveyard to the battlefield. + this.getSpellAbility().addEffect(new ReturnFromGraveyardToBattlefieldTargetEffect()); + this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_CREATURE_YOUR_GRAVEYARD)); + this.getSpellAbility().withFirstModeCost(new GenericManaCost(2)); + + // + {1} -- Return target Mount or Vehicle card from your graveyard to the battlefield. + this.getSpellAbility().addMode( + new Mode(new ReturnFromGraveyardToBattlefieldTargetEffect()) + .addTarget(new TargetCardInYourGraveyard(filter)) + .withCost(new GenericManaCost(1)) + ); + + // + {1} -- Return target Aura or Equipment card from your graveyard to the battlefield attached to a creature you control. + this.getSpellAbility().addMode( + new Mode(new OneLastJobEffect()) + .addTarget(new TargetCardInYourGraveyard(filter2)) + .withCost(new GenericManaCost(1)) + ); + } + + private OneLastJob(final OneLastJob card) { + super(card); + } + + @Override + public OneLastJob copy() { + return new OneLastJob(this); + } +} + +// Inspired vaguely by Academy Researcher and Gryff's Boon +class OneLastJobEffect extends OneShotEffect { + + OneLastJobEffect() { + super(Outcome.PutCardInPlay); + staticText = "Return target Aura or Equipment card from your graveyard to the battlefield attached to a creature you control"; + } + + private OneLastJobEffect(final OneLastJobEffect effect) { + super(effect); + } + + @Override + public OneLastJobEffect copy() { + return new OneLastJobEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Card cardToReturn = game.getCard(source.getFirstTarget()); + Player controller = game.getPlayer(source.getControllerId()); + if (cardToReturn == null || controller == null) { + return false; + } + + FilterPermanent filter = new FilterControlledCreaturePermanent( + "a creature you control that " + cardToReturn.getLogName() + " can be attached to" + ); + filter.add(new CanBeEnchantedByPredicate(cardToReturn)); + Target target = new TargetPermanent(filter); + target.withNotTarget(true); + controller.choose(Outcome.BoostCreature, target, source, game); + + Permanent permanent = game.getPermanent(target.getFirstTarget()); + if (permanent != null) { + game.informPlayers( + "Chosen creature to attach " + cardToReturn.getLogName() + + " to:" + permanent.getLogName() + + CardUtil.getSourceLogName(game, source) + ); + game.getState().setValue("attachTo:" + cardToReturn.getId(), permanent); + controller.moveCards(cardToReturn, Zone.BATTLEFIELD, source, game); + permanent.addAttachment(cardToReturn.getId(), source, game); + return true; + } + + SubTypes subTypes = cardToReturn.getSubtype(game); + if (subTypes.contains(SubType.AURA)) { + // For Auras, we can only attempt to return if a valid choice was chosen + game.informPlayers( + "No creatures controlled by " + controller.getLogName() + + " to return " + cardToReturn.getLogName() + + " attached to" + CardUtil.getSourceLogName(game, source) + ); + return true; + } + + // For non-Aura, card is returned not attached to anything. + controller.moveCards(cardToReturn, Zone.BATTLEFIELD, source, game); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/o/OneWithTheMultiverse.java b/Mage.Sets/src/mage/cards/o/OneWithTheMultiverse.java index dc7ac80b6b1..fbffad823ab 100644 --- a/Mage.Sets/src/mage/cards/o/OneWithTheMultiverse.java +++ b/Mage.Sets/src/mage/cards/o/OneWithTheMultiverse.java @@ -7,7 +7,7 @@ import mage.abilities.common.SimpleStaticAbility; import mage.abilities.condition.Condition; import mage.abilities.effects.AsThoughEffectImpl; import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; -import mage.abilities.effects.common.continuous.PlayTheTopCardEffect; +import mage.abilities.effects.common.continuous.PlayFromTopOfLibraryEffect; import mage.abilities.hint.common.ConditionPermanentHint; import mage.cards.Card; import mage.cards.CardImpl; @@ -36,7 +36,7 @@ public final class OneWithTheMultiverse extends CardImpl { this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); // You may play lands and cast spells from the top of your library. - this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect())); + this.addAbility(new SimpleStaticAbility(new PlayFromTopOfLibraryEffect())); // Once during each of your turns, you may cast a spell from your hand or the top of your library without paying its mana cost. this.addAbility( diff --git a/Mage.Sets/src/mage/cards/o/OracleOfMulDaya.java b/Mage.Sets/src/mage/cards/o/OracleOfMulDaya.java index a5a6bc5d02a..c59a1fce09c 100644 --- a/Mage.Sets/src/mage/cards/o/OracleOfMulDaya.java +++ b/Mage.Sets/src/mage/cards/o/OracleOfMulDaya.java @@ -3,7 +3,7 @@ package mage.cards.o; import mage.MageInt; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.continuous.PlayAdditionalLandsControllerEffect; -import mage.abilities.effects.common.continuous.PlayTheTopCardEffect; +import mage.abilities.effects.common.continuous.PlayFromTopOfLibraryEffect; import mage.abilities.effects.common.continuous.PlayWithTheTopCardRevealedEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -40,7 +40,7 @@ public final class OracleOfMulDaya extends CardImpl { this.addAbility(new SimpleStaticAbility(new PlayWithTheTopCardRevealedEffect())); // You may play lands from the top of your library. - this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(TargetController.YOU, filter, false))); + this.addAbility(new SimpleStaticAbility(new PlayFromTopOfLibraryEffect(filter))); } private OracleOfMulDaya(final OracleOfMulDaya card) { diff --git a/Mage.Sets/src/mage/cards/o/OrneryTumblewagg.java b/Mage.Sets/src/mage/cards/o/OrneryTumblewagg.java new file mode 100644 index 00000000000..7545d05d313 --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OrneryTumblewagg.java @@ -0,0 +1,62 @@ +package mage.cards.o; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksWhileSaddledTriggeredAbility; +import mage.abilities.common.BeginningOfCombatTriggeredAbility; +import mage.abilities.effects.common.DoubleCountersTargetEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.keyword.SaddleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.TargetController; +import mage.counters.CounterType; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class OrneryTumblewagg extends CardImpl { + + public OrneryTumblewagg(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.subtype.add(SubType.BRUSHWAGG); + this.subtype.add(SubType.MOUNT); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // At the beginning of combat on your turn, put a +1/+1 counter on target creature. + Ability ability = new BeginningOfCombatTriggeredAbility( + new AddCountersTargetEffect(CounterType.P1P1.createInstance()), + TargetController.YOU, false + ); + ability.addTarget(new TargetCreaturePermanent()); + this.addAbility(ability); + + // Whenever Ornery Tumblewagg attacks while saddled, double the number of +1/+1 counters on target creature. + ability = new AttacksWhileSaddledTriggeredAbility( + new DoubleCountersTargetEffect(CounterType.P1P1) + .setText("double the number of +1/+1 counters on target creature") + ); + ability.addTarget(new TargetCreaturePermanent()); + this.addAbility(ability); + + // Saddle 2 + this.addAbility(new SaddleAbility(2)); + + } + + private OrneryTumblewagg(final OrneryTumblewagg card) { + super(card); + } + + @Override + public OrneryTumblewagg copy() { + return new OrneryTumblewagg(this); + } +} diff --git a/Mage.Sets/src/mage/cards/o/OutcasterGreenblade.java b/Mage.Sets/src/mage/cards/o/OutcasterGreenblade.java new file mode 100644 index 00000000000..fc329409095 --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OutcasterGreenblade.java @@ -0,0 +1,65 @@ +package mage.cards.o; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; +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.predicate.Predicates; +import mage.target.common.TargetCardInLibrary; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class OutcasterGreenblade extends CardImpl { + + private static final FilterCard filter = new FilterCard("a basic land card or a Desert card"); + + static { + filter.add(Predicates.or(SuperType.BASIC.getPredicate(), SubType.DESERT.getPredicate())); + } + + private static final DynamicValue xValue = + new PermanentsOnBattlefieldCount(new FilterControlledPermanent(SubType.DESERT)); + + public OutcasterGreenblade(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.MERCENARY); + this.power = new MageInt(1); + this.toughness = new MageInt(2); + + // When Outcaster Greenblade enters the battlefield, search your library for a basic land card or a Desert card, reveal it, put it into your hand, then shuffle. + this.addAbility(new EntersBattlefieldTriggeredAbility( + new SearchLibraryPutInHandEffect(new TargetCardInLibrary(filter), true) + )); + + // Outcaster Greenblade gets +1/+1 for each Desert you control. + this.addAbility(new SimpleStaticAbility( + new BoostSourceEffect(xValue, xValue, Duration.WhileOnBattlefield) + .setText("{this} gets +1/+1 for each Desert you control") + )); + } + + private OutcasterGreenblade(final OutcasterGreenblade card) { + super(card); + } + + @Override + public OutcasterGreenblade copy() { + return new OutcasterGreenblade(this); + } +} diff --git a/Mage.Sets/src/mage/cards/o/OutcasterTrailblazer.java b/Mage.Sets/src/mage/cards/o/OutcasterTrailblazer.java new file mode 100644 index 00000000000..fe76848df61 --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OutcasterTrailblazer.java @@ -0,0 +1,60 @@ +package mage.cards.o; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.mana.AddManaOfAnyColorEffect; +import mage.abilities.keyword.PlotAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.filter.predicate.mageobject.PowerPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class OutcasterTrailblazer extends CardImpl { + + private static final FilterPermanent filter + = new FilterControlledCreaturePermanent("another creature with power 4 or greater"); + + static { + filter.add(AnotherPredicate.instance); + filter.add(new PowerPredicate(ComparisonType.MORE_THAN, 3)); + } + + public OutcasterTrailblazer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.DRUID); + this.power = new MageInt(4); + this.toughness = new MageInt(2); + + // When Outcaster Trailblazer enters the battlefield, add one mana of any color. + this.addAbility(new EntersBattlefieldTriggeredAbility(new AddManaOfAnyColorEffect())); + + // Whenever another creature with power 4 or greater enters the battlefield under your control, draw a card. + this.addAbility(new EntersBattlefieldControlledTriggeredAbility(new DrawCardSourceControllerEffect(1), filter)); + + // Plot {2}{G} + this.addAbility(new PlotAbility("{2}{G}")); + } + + private OutcasterTrailblazer(final OutcasterTrailblazer card) { + super(card); + } + + @Override + public OutcasterTrailblazer copy() { + return new OutcasterTrailblazer(this); + } +} diff --git a/Mage.Sets/src/mage/cards/o/OutlawMedic.java b/Mage.Sets/src/mage/cards/o/OutlawMedic.java new file mode 100644 index 00000000000..e89355551ab --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OutlawMedic.java @@ -0,0 +1,42 @@ +package mage.cards.o; + +import mage.MageInt; +import mage.abilities.common.DiesSourceTriggeredAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.keyword.LifelinkAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class OutlawMedic extends CardImpl { + + public OutlawMedic(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(1); + this.toughness = new MageInt(3); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // When Outlaw Medic dies, draw a card. + this.addAbility(new DiesSourceTriggeredAbility(new DrawCardSourceControllerEffect(1))); + } + + private OutlawMedic(final OutlawMedic card) { + super(card); + } + + @Override + public OutlawMedic copy() { + return new OutlawMedic(this); + } +} diff --git a/Mage.Sets/src/mage/cards/o/OutlawStitcher.java b/Mage.Sets/src/mage/cards/o/OutlawStitcher.java new file mode 100644 index 00000000000..b9ab5d0a537 --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OutlawStitcher.java @@ -0,0 +1,123 @@ +package mage.cards.o; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.abilities.keyword.PlotAbility; +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.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.ZombieRogueToken; +import mage.watchers.common.CastSpellLastTurnWatcher; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class OutlawStitcher extends CardImpl { + + private static final Hint hint = new ValueHint("Spells you've cast this turn other than the first", OutlawStitcherDynamicValue.instance); + + public OutlawStitcher(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARLOCK); + this.power = new MageInt(1); + this.toughness = new MageInt(4); + + // When Outlaw Stitcher enters the battlefield, create a 2/2 blue and black Zombie Rogue creature token, then put two +1/+1 counters on that token for each spell you've cast this turn other than the first. + this.addAbility(new EntersBattlefieldTriggeredAbility(new OutlawStitcherEffect()).addHint(hint)); + + // Plot {4}{U} + this.addAbility(new PlotAbility("{4}{U}")); + } + + private OutlawStitcher(final OutlawStitcher card) { + super(card); + } + + @Override + public OutlawStitcher copy() { + return new OutlawStitcher(this); + } +} + +enum OutlawStitcherDynamicValue implements DynamicValue { + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + int spellCastThisTurn = game.getState() + .getWatcher(CastSpellLastTurnWatcher.class) + .getAmountOfSpellsPlayerCastOnCurrentTurn(sourceAbility.getControllerId()); + if (spellCastThisTurn <= 1) { + return 0; + } + return spellCastThisTurn - 1; + } + + @Override + public OutlawStitcherDynamicValue copy() { + return this; + } + + @Override + public String toString() { + return "1"; + } + + @Override + public String getMessage() { + return "spell you've cast this turn other than the first"; + } +} + +class OutlawStitcherEffect extends OneShotEffect { + + OutlawStitcherEffect() { + super(Outcome.PutCreatureInPlay); + staticText = "create a 2/2 blue and black Zombie Rogue creature token, " + + "then put two +1/+1 counters on that token for each spell you've cast this turn other than the first"; + } + + private OutlawStitcherEffect(final OutlawStitcherEffect effect) { + super(effect); + } + + @Override + public OutlawStitcherEffect copy() { + return new OutlawStitcherEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + CreateTokenEffect effect = new CreateTokenEffect(new ZombieRogueToken()); + boolean result = effect.apply(game, source); + int xvalue = OutlawStitcherDynamicValue.instance.calculate(game, source, this); + if (xvalue <= 0 || !result) { + return result; + } + for (UUID id : effect.getLastAddedTokenIds()) { + Permanent token = game.getPermanent(id); + if (token == null) { + continue; + } + token.addCounters(CounterType.P1P1.createInstance(2 * xvalue), source.getControllerId(), source, game); + } + return true; + } + +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/o/OutlawsFury.java b/Mage.Sets/src/mage/cards/o/OutlawsFury.java new file mode 100644 index 00000000000..e6555752a0e --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OutlawsFury.java @@ -0,0 +1,55 @@ +package mage.cards.o; + +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.ExileTopXMayPlayUntilEffect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.hint.ConditionHint; +import mage.abilities.hint.Hint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.mageobject.OutlawPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class OutlawsFury extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledPermanent(); + + static { + filter.add(OutlawPredicate.instance); + } + + private static final Condition condition = new PermanentsOnTheBattlefieldCondition(filter); + private static final Hint hint = new ConditionHint(condition, "You control an outlaw"); + + public OutlawsFury(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{R}"); + + // Creatures you control get +2/+0 until end of turn. If you control an outlaw, exile the top card of your library. Until the end of your next turn, you may play that card. + this.getSpellAbility().addEffect(new BoostControlledEffect(2, 0, Duration.EndOfTurn)); + this.getSpellAbility().addEffect(new ConditionalOneShotEffect( + new ExileTopXMayPlayUntilEffect(1, Duration.UntilEndOfYourNextTurn), + condition, "if you control an outlaw, exile the top card of your library. " + + "Until the end of your next turn, you may play that card" + )); + this.getSpellAbility().addHint(hint); + } + + private OutlawsFury(final OutlawsFury card) { + super(card); + } + + @Override + public OutlawsFury copy() { + return new OutlawsFury(this); + } +} diff --git a/Mage.Sets/src/mage/cards/o/Outwit.java b/Mage.Sets/src/mage/cards/o/Outwit.java index a3ca8264e09..46491d78ad6 100644 --- a/Mage.Sets/src/mage/cards/o/Outwit.java +++ b/Mage.Sets/src/mage/cards/o/Outwit.java @@ -1,42 +1,35 @@ package mage.cards.o; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; -import mage.abilities.Ability; import mage.abilities.effects.common.CounterTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Zone; -import mage.filter.Filter; +import mage.filter.FilterPlayer; import mage.filter.FilterSpell; -import mage.filter.StaticFilters; -import mage.game.Game; -import mage.game.stack.Spell; -import mage.game.stack.StackObject; -import mage.players.Player; -import mage.target.Target; -import mage.target.TargetObject; +import mage.filter.predicate.mageobject.TargetsPlayerPredicate; +import mage.target.TargetSpell; +import java.util.UUID; /** - * - * @author jeffwadsworth + * @author Susucr */ public final class Outwit extends CardImpl { private static FilterSpell filter = new FilterSpell("spell that targets a player"); - public Outwit(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{U}"); + static { + filter.add(new TargetsPlayerPredicate(new FilterPlayer())); + } + public Outwit(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{U}"); // Counter target spell that targets a player. this.getSpellAbility().addEffect(new CounterTargetEffect()); - this.getSpellAbility().addTarget(new CustomTargetSpell(filter)); + this.getSpellAbility().addTarget(new TargetSpell(filter)); } private Outwit(final Outwit card) { @@ -47,111 +40,4 @@ public final class Outwit extends CardImpl { public Outwit copy() { return new Outwit(this); } - - private static class CustomTargetSpell extends TargetObject { - - protected FilterSpell filter; - - public CustomTargetSpell() { - this(1, 1, StaticFilters.FILTER_SPELL); - } - - public CustomTargetSpell(FilterSpell filter) { - this(1, 1, filter); - } - - public CustomTargetSpell(int numTargets, FilterSpell filter) { - this(numTargets, numTargets, filter); - } - - public CustomTargetSpell(int minNumTargets, int maxNumTargets, FilterSpell filter) { - this.minNumberOfTargets = minNumTargets; - this.maxNumberOfTargets = maxNumTargets; - this.zone = Zone.STACK; - this.filter = filter; - this.targetName = filter.getMessage(); - } - - private CustomTargetSpell(final CustomTargetSpell target) { - super(target); - this.filter = target.filter.copy(); - } - - @Override - public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - return canChoose(sourceControllerId, game); - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - return possibleTargets(sourceControllerId, game); - } - - @Override - public boolean canTarget(UUID id, Ability source, Game game) { - if (super.canTarget(id, source, game)) { - if (targetsPlayer(id, game)) { - return true; - } - } - return false; - } - - @Override - public boolean canChoose(UUID sourceControllerId, Game game) { - int count = 0; - for (StackObject stackObject : game.getStack()) { - if (stackObject instanceof Spell && filter.match(stackObject, game)) { - if (targetsPlayer(stackObject.getId(), game)) { - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - } - } - return false; - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { - Set possibleTargets = new HashSet<>(); - for (StackObject stackObject : game.getStack()) { - if (stackObject instanceof Spell && filter.match(stackObject, game)) { - if (targetsPlayer(stackObject.getId(), game)) { - possibleTargets.add(stackObject.getId()); - } - } - } - return possibleTargets; - } - - @Override - public Filter getFilter() { - return filter; - } - - private boolean targetsPlayer(UUID id, Game game) { - StackObject spell = game.getStack().getStackObject(id); - if (spell != null) { - Ability ability = spell.getStackAbility(); - if (ability != null && !ability.getTargets().isEmpty()) { - for (Target target : ability.getTargets()) { - for (UUID playerId : target.getTargets()) { - Player player = game.getPlayer(playerId); - if (player != null) { - return true; - } - } - } - } - } - return false; - } - - @Override - public CustomTargetSpell copy() { - return new CustomTargetSpell(this); - } - } } diff --git a/Mage.Sets/src/mage/cards/o/OverseerOfVault76.java b/Mage.Sets/src/mage/cards/o/OverseerOfVault76.java new file mode 100644 index 00000000000..7319e5ed488 --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OverseerOfVault76.java @@ -0,0 +1,80 @@ +package mage.cards.o; + +import java.util.UUID; + +import mage.MageInt; +import mage.abilities.common.BeginningOfCombatTriggeredAbility; +import mage.abilities.common.EntersBattlefieldThisOrAnotherTriggeredAbility; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.common.RemoveCounterCost; +import mage.abilities.effects.common.DoWhenCostPaid; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.VigilanceAbility; +import mage.constants.*; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.PowerPredicate; +import mage.target.TargetPermanent; + +/** + * @author Cguy7777 + */ +public final class OverseerOfVault76 extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with power 3 or less"); + + static { + filter.add(new PowerPredicate(ComparisonType.FEWER_THAN, 4)); + } + + public OverseerOfVault76(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.ADVISOR); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // First Contact -- Whenever Overseer of Vault 76 or another creature with power 3 or less + // enters the battlefield under your control, put a quest counter on Overseer of Vault 76. + this.addAbility(new EntersBattlefieldThisOrAnotherTriggeredAbility( + new AddCountersSourceEffect(CounterType.QUEST.createInstance()), filter, false, true) + .withFlavorWord("First Contact")); + + // At the beginning of combat on your turn, you may remove three quest counters from + // among permanents you control. When you do, put a +1/+1 counter on each + // creature you control and they gain vigilance until end of turn. + ReflexiveTriggeredAbility boostAbility = new ReflexiveTriggeredAbility( + new AddCountersAllEffect(CounterType.P1P1.createInstance(), StaticFilters.FILTER_CONTROLLED_CREATURE), + false); + boostAbility.addEffect(new GainAbilityControlledEffect( + VigilanceAbility.getInstance(), Duration.EndOfTurn, StaticFilters.FILTER_CONTROLLED_CREATURE) + .setText("and they gain vigilance until end of turn")); + + Cost cost = new RemoveCounterCost( + new TargetPermanent(1, 3, StaticFilters.FILTER_CONTROLLED_PERMANENTS), + CounterType.QUEST, + 3) + .setText("remove three quest counters from among permanents you control"); + this.addAbility(new BeginningOfCombatTriggeredAbility( + new DoWhenCostPaid(boostAbility, cost, "Remove three quest counters from among permanents you control?"), + TargetController.YOU, + false)); + } + + private OverseerOfVault76(final OverseerOfVault76 card) { + super(card); + } + + @Override + public OverseerOfVault76 copy() { + return new OverseerOfVault76(this); + } +} diff --git a/Mage.Sets/src/mage/cards/o/OverzealousMuscle.java b/Mage.Sets/src/mage/cards/o/OverzealousMuscle.java new file mode 100644 index 00000000000..d84747f29bc --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OverzealousMuscle.java @@ -0,0 +1,66 @@ +package mage.cards.o; + +import mage.MageInt; +import mage.abilities.common.CommittedCrimeTriggeredAbility; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +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.game.Game; +import mage.game.events.GameEvent; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class OverzealousMuscle extends CardImpl { + + public OverzealousMuscle(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{B}"); + + this.subtype.add(SubType.OGRE); + this.subtype.add(SubType.MERCENARY); + this.power = new MageInt(5); + this.toughness = new MageInt(4); + + // Whenever you commit a crime during your turn, Overzealous Muscle gains indestructible until end of turn. + this.addAbility(new OverzealousMuscleTriggeredAbility()); + } + + private OverzealousMuscle(final OverzealousMuscle card) { + super(card); + } + + @Override + public OverzealousMuscle copy() { + return new OverzealousMuscle(this); + } +} + +class OverzealousMuscleTriggeredAbility extends CommittedCrimeTriggeredAbility { + + OverzealousMuscleTriggeredAbility() { + super(new GainAbilitySourceEffect(IndestructibleAbility.getInstance(), Duration.EndOfTurn)); + this.setTriggerPhrase("Whenever you commit a crime during your turn, "); + } + + private OverzealousMuscleTriggeredAbility(final OverzealousMuscleTriggeredAbility ability) { + super(ability); + } + + @Override + public OverzealousMuscleTriggeredAbility copy() { + return new OverzealousMuscleTriggeredAbility(this); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return super.checkTrigger(event, game) + && game.isActivePlayer(getControllerId()); + } + +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/o/OvikaEnigmaGoliath.java b/Mage.Sets/src/mage/cards/o/OvikaEnigmaGoliath.java index 2fdc9d4ad6f..dba2368673d 100644 --- a/Mage.Sets/src/mage/cards/o/OvikaEnigmaGoliath.java +++ b/Mage.Sets/src/mage/cards/o/OvikaEnigmaGoliath.java @@ -9,14 +9,12 @@ import mage.abilities.costs.CompositeCost; import mage.abilities.costs.common.PayLifeCost; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; import mage.abilities.keyword.HasteAbility; import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.SuperType; import mage.abilities.keyword.FlyingAbility; -import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.keyword.WardAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -47,7 +45,7 @@ public final class OvikaEnigmaGoliath extends CardImpl { // Ward--{3}, Pay 3 life. this.addAbility(new WardAbility(new CompositeCost( - new GenericManaCost(2), new PayLifeCost(3), "{3}, Pay 3 life" + new GenericManaCost(3), new PayLifeCost(3), "{3}, Pay 3 life" ), false)); // Whenever you cast a noncreature spell, create X 1/1 red Phyrexian Goblin creature tokens, where X is the mana value of that spell. They gain haste until end of turn. diff --git a/Mage.Sets/src/mage/cards/p/PaladinDanseSteelMaverick.java b/Mage.Sets/src/mage/cards/p/PaladinDanseSteelMaverick.java new file mode 100644 index 00000000000..58d8a2a24d7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PaladinDanseSteelMaverick.java @@ -0,0 +1,67 @@ +package mage.cards.p; + +import mage.MageInt; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.ExileSourceCost; +import mage.abilities.effects.common.continuous.GainAbilityAllEffect; +import mage.abilities.keyword.IndestructibleAbility; +import mage.abilities.keyword.LifelinkAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; + +import java.util.UUID; + +/** + * + * @author justinjohnson14 + */ +public final class PaladinDanseSteelMaverick extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(); + + static { + filter.add(Predicates.or( + CardType.ARTIFACT.getPredicate(), + SubType.HUMAN.getPredicate() + )); + } + + public PaladinDanseSteelMaverick(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{2}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SYNTH); + this.subtype.add(SubType.KNIGHT); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // Exile Paladin Danse, Steel Maverick: Each creature you control that's an artifact or Human gains indestructible until end of turn. + this.addAbility(new SimpleActivatedAbility( + new GainAbilityAllEffect(IndestructibleAbility.getInstance(), Duration.EndOfTurn, filter) + .setText("Each creature you control that's an artifact or Human gains indestructible until end of turn"), + new ExileSourceCost() + )); + } + + private PaladinDanseSteelMaverick(final PaladinDanseSteelMaverick card) { + super(card); + } + + @Override + public PaladinDanseSteelMaverick copy() { + return new PaladinDanseSteelMaverick(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PatientNaturalist.java b/Mage.Sets/src/mage/cards/p/PatientNaturalist.java new file mode 100644 index 00000000000..4e16c83d43a --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PatientNaturalist.java @@ -0,0 +1,44 @@ +package mage.cards.p; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.MillThenPutInHandEffect; +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 PatientNaturalist extends CardImpl { + + public PatientNaturalist(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SCOUT); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // When Patient Naturalist enters the battlefield, mill three cards. Put a land card from among the milled cards into your hand. If you can't, create a Treasure token. + this.addAbility(new EntersBattlefieldTriggeredAbility(new MillThenPutInHandEffect( + 3, StaticFilters.FILTER_CARD_LAND_A, + new CreateTokenEffect(new TreasureToken()), false + ))); + } + + private PatientNaturalist(final PatientNaturalist card) { + super(card); + } + + @Override + public PatientNaturalist copy() { + return new PatientNaturalist(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PeerlessRopemaster.java b/Mage.Sets/src/mage/cards/p/PeerlessRopemaster.java new file mode 100644 index 00000000000..e30ad63dbef --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PeerlessRopemaster.java @@ -0,0 +1,51 @@ +package mage.cards.p; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.ReturnToHandTargetEffect; +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.permanent.TappedPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class PeerlessRopemaster extends CardImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent("tapped creature"); + + static { + filter.add(TappedPredicate.TAPPED); + } + + public PeerlessRopemaster(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{U}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // When Peerless Ropemaster enters the battlefield, return up to one target tapped creature to its owner's hand. + Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnToHandTargetEffect()); + ability.addTarget(new TargetPermanent(0, 1, filter)); + this.addAbility(ability); + } + + private PeerlessRopemaster(final PeerlessRopemaster card) { + super(card); + } + + @Override + public PeerlessRopemaster copy() { + return new PeerlessRopemaster(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PerimeterEnforcer.java b/Mage.Sets/src/mage/cards/p/PerimeterEnforcer.java new file mode 100644 index 00000000000..1829d1b45da --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PerimeterEnforcer.java @@ -0,0 +1,66 @@ +package mage.cards.p; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; +import mage.abilities.common.TurnedFaceUpAllTriggeredAbility; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.LifelinkAbility; +import mage.abilities.meta.OrTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class PerimeterEnforcer extends CardImpl { + + private static final FilterPermanent filter1 = new FilterPermanent(SubType.DETECTIVE, "another Detective"); + private static final FilterPermanent filter2 = new FilterPermanent(SubType.DETECTIVE, "a Detective you control"); + + static { + filter1.add(AnotherPredicate.instance); + filter2.add(TargetController.YOU.getControllerPredicate()); + } + + + public PerimeterEnforcer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.DETECTIVE); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // Whenever another Detective enters the battlefield under your control and whenever a Detective you control is turned face up, Perimeter Enforcer gets +1/+1 until end of turn. + this.addAbility(new OrTriggeredAbility( + Zone.BATTLEFIELD, + new BoostSourceEffect(1, 1, Duration.EndOfTurn), + false, + "Whenever another Detective enters the battlefield under your control and " + + "whenever a Detective you control is turned face up, ", + new EntersBattlefieldControlledTriggeredAbility(Zone.BATTLEFIELD, null, filter1, false), + new TurnedFaceUpAllTriggeredAbility(null, filter2) + )); + } + + private PerimeterEnforcer(final PerimeterEnforcer card) { + super(card); + } + + @Override + public PerimeterEnforcer copy() { + return new PerimeterEnforcer(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PestControl.java b/Mage.Sets/src/mage/cards/p/PestControl.java new file mode 100644 index 00000000000..cfbbd44a76f --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PestControl.java @@ -0,0 +1,46 @@ +package mage.cards.p; + +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DestroyAllEffect; +import mage.abilities.keyword.CyclingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterNonlandPermanent; +import mage.filter.predicate.mageobject.ManaValuePredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class PestControl extends CardImpl { + + private static final FilterPermanent filter + = new FilterNonlandPermanent("nonland permanents with mana value 1 or less"); + + static { + filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, 2)); + } + + public PestControl(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{W}{B}"); + + // Destroy all nonland permanents with mana value 1 or less. + this.getSpellAbility().addEffect(new DestroyAllEffect(filter)); + + // Cycling {2} + this.addAbility(new CyclingAbility(new ManaCostsImpl<>("{2}"))); + } + + private PestControl(final PestControl card) { + super(card); + } + + @Override + public PestControl copy() { + return new PestControl(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PhantomInterference.java b/Mage.Sets/src/mage/cards/p/PhantomInterference.java new file mode 100644 index 00000000000..94b68bc9b29 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PhantomInterference.java @@ -0,0 +1,45 @@ +package mage.cards.p; + +import mage.abilities.Mode; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.CounterUnlessPaysEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.SpreeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.game.permanent.token.Spirit22Token; +import mage.target.TargetSpell; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class PhantomInterference extends CardImpl { + + public PhantomInterference(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{U}"); + + // Spree + this.addAbility(new SpreeAbility(this)); + + // + {3} -- Create a 2/2 white Spirit creature token with flying. + this.getSpellAbility().addEffect(new CreateTokenEffect(new Spirit22Token())); + this.getSpellAbility().withFirstModeCost(new GenericManaCost(3)); + + // + {1} -- Counter target spell unless its controller pays {2}. + this.getSpellAbility().addMode(new Mode(new CounterUnlessPaysEffect(new GenericManaCost(2))) + .addTarget(new TargetSpell()) + .withCost(new GenericManaCost(1))); + } + + private PhantomInterference(final PhantomInterference card) { + super(card); + } + + @Override + public PhantomInterference copy() { + return new PhantomInterference(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PhyrexianNegator.java b/Mage.Sets/src/mage/cards/p/PhyrexianNegator.java index ef76d274ea0..f5f462bbf71 100644 --- a/Mage.Sets/src/mage/cards/p/PhyrexianNegator.java +++ b/Mage.Sets/src/mage/cards/p/PhyrexianNegator.java @@ -15,7 +15,6 @@ import mage.constants.Zone; import mage.filter.FilterPermanent; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; import mage.players.Player; import mage.target.targetpointer.FixedTarget; /** @@ -64,7 +63,7 @@ class PhyrexianNegatorTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT; } @Override diff --git a/Mage.Sets/src/mage/cards/p/PhyrexianTotem.java b/Mage.Sets/src/mage/cards/p/PhyrexianTotem.java index 82d7c5e07c8..af89b977e0d 100644 --- a/Mage.Sets/src/mage/cards/p/PhyrexianTotem.java +++ b/Mage.Sets/src/mage/cards/p/PhyrexianTotem.java @@ -99,7 +99,7 @@ class PhyrexianTotemTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT; } @Override diff --git a/Mage.Sets/src/mage/cards/p/PillageTheBog.java b/Mage.Sets/src/mage/cards/p/PillageTheBog.java new file mode 100644 index 00000000000..a0f94c6c155 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PillageTheBog.java @@ -0,0 +1,49 @@ +package mage.cards.p; + +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.MultipliedValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.LookLibraryAndPickControllerEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.abilities.keyword.PlotAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.PutCards; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class PillageTheBog extends CardImpl { + + static final DynamicValue value = new PermanentsOnBattlefieldCount(StaticFilters.FILTER_CONTROLLED_PERMANENT_LANDS); + static final DynamicValue xValue = new MultipliedValue(value, 2); + private static final Hint hint = new ValueHint("lands you control", value); + + public PillageTheBog(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{B}{G}"); + + // Look at the top X cards of your library, where X is twice the number of lands you control. Put one of them into your hand and the rest on the bottom of your library in a random order. + this.getSpellAbility().addEffect( + new LookLibraryAndPickControllerEffect(xValue, 1, PutCards.HAND, PutCards.BOTTOM_RANDOM) + .setText("Look at the top X cards of your library, where X is twice the number of lands you control. " + + "Put one of them into your hand and the rest on the bottom of your library in a random order.")); + this.getSpellAbility().addHint(hint); + + // Plot {1}{B}{G} + this.addAbility(new PlotAbility("{1}{B}{G}")); + } + + private PillageTheBog(final PillageTheBog card) { + super(card); + } + + @Override + public PillageTheBog copy() { + return new PillageTheBog(this); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/p/PiousWarrior.java b/Mage.Sets/src/mage/cards/p/PiousWarrior.java index e5afe8e378a..892927fc46e 100644 --- a/Mage.Sets/src/mage/cards/p/PiousWarrior.java +++ b/Mage.Sets/src/mage/cards/p/PiousWarrior.java @@ -13,7 +13,7 @@ import mage.constants.SubType; import mage.constants.Outcome; import mage.constants.Zone; import mage.game.Game; -import mage.game.events.DamagedEvent; +import mage.game.events.DamagedBatchForOnePermanentEvent; import mage.game.events.GameEvent; import mage.players.Player; @@ -64,14 +64,18 @@ class PiousWarriorTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT; } @Override public boolean checkTrigger(GameEvent event, Game game) { - if (event.getTargetId().equals(this.sourceId) && ((DamagedEvent)event).isCombatDamage() ) { - this.getEffects().get(0).setValue("damageAmount", event.getAmount()); - return true; + + DamagedBatchForOnePermanentEvent dEvent = (DamagedBatchForOnePermanentEvent) event; + int damage = dEvent.getAmount(); + + if (event.getTargetId().equals(this.sourceId) && dEvent.isCombatDamage() && damage > 0) { + this.getEffects().setValue("damageAmount", damage); + return true; } return false; } diff --git a/Mage.Sets/src/mage/cards/p/PipBoy3000.java b/Mage.Sets/src/mage/cards/p/PipBoy3000.java new file mode 100644 index 00000000000..2a5c8786a16 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PipBoy3000.java @@ -0,0 +1,58 @@ +package mage.cards.p; + +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.common.AttacksAttachedTriggeredAbility; +import mage.abilities.effects.common.DrawDiscardControllerEffect; +import mage.abilities.effects.common.UntapTargetEffect; +import mage.abilities.effects.common.counter.AddCountersAttachedEffect; +import mage.abilities.keyword.EquipAbility; +import mage.constants.SubType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.target.TargetPermanent; + +/** + * @author Cguy7777 + */ +public final class PipBoy3000 extends CardImpl { + + public PipBoy3000(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}"); + + this.subtype.add(SubType.EQUIPMENT); + + // Whenever equipped creature attacks, choose one -- + // * Sort Inventory -- Draw a card, then discard a card. + Ability ability = new AttacksAttachedTriggeredAbility(new DrawDiscardControllerEffect()) + .withFirstModeFlavorWord("Sort Inventory"); + + // * Pick a Perk -- Put a +1/+1 counter on that creature. + ability.addMode(new Mode( + new AddCountersAttachedEffect(CounterType.P1P1.createInstance(), "that creature")) + .withFlavorWord("Pick a Perk")); + + // * Check Map -- Untap up to two target lands. + Mode mode = new Mode(new UntapTargetEffect()); + mode.addTarget(new TargetPermanent(0, 2, StaticFilters.FILTER_LANDS)); + ability.addMode(mode.withFlavorWord("Check Map")); + this.addAbility(ability); + + // Equip {2} + this.addAbility(new EquipAbility(2, false)); + } + + private PipBoy3000(final PipBoy3000 card) { + super(card); + } + + @Override + public PipBoy3000 copy() { + return new PipBoy3000(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PiperWrightPublickReporter.java b/Mage.Sets/src/mage/cards/p/PiperWrightPublickReporter.java new file mode 100644 index 00000000000..953c1f6690b --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PiperWrightPublickReporter.java @@ -0,0 +1,57 @@ +package mage.cards.p; + +import java.util.UUID; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.common.SacrificePermanentTriggeredAbility; +import mage.abilities.dynamicvalue.common.SavedDamageValue; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.effects.keyword.InvestigateEffect; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.target.common.TargetControlledCreaturePermanent; + +/** + * @author Cguy7777 + */ +public final class PiperWrightPublickReporter extends CardImpl { + + public PiperWrightPublickReporter(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.DETECTIVE); + this.power = new MageInt(1); + this.toughness = new MageInt(2); + + // Whenever Piper Wright deals combat damage to a player, investigate that many times. + this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility( + new InvestigateEffect(SavedDamageValue.MANY) + .setText("investigate that many times. (To investigate, create a Clue token. " + + "It's an artifact with \"{2}, Sacrifice this artifact: Draw a card.\")"), + false)); + + // Whenever you sacrifice a Clue, put a +1/+1 counter on target creature you control. + Ability ability = new SacrificePermanentTriggeredAbility( + new AddCountersTargetEffect(CounterType.P1P1.createInstance()), StaticFilters.FILTER_CONTROLLED_CLUE); + ability.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(ability); + } + + private PiperWrightPublickReporter(final PiperWrightPublickReporter card) { + super(card); + } + + @Override + public PiperWrightPublickReporter copy() { + return new PiperWrightPublickReporter(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PitilessCarnage.java b/Mage.Sets/src/mage/cards/p/PitilessCarnage.java new file mode 100644 index 00000000000..54310f9c908 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PitilessCarnage.java @@ -0,0 +1,87 @@ +package mage.cards.p; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.PlotAbility; +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.players.Player; +import mage.target.common.TargetSacrifice; + +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * @author TheElk801 + */ +public final class PitilessCarnage extends CardImpl { + + public PitilessCarnage(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{B}"); + + // Sacrifice any number of permanents you control, then that many cards. + this.getSpellAbility().addEffect(new PitilessCarnageEffect()); + + // Plot {1}{B}{B} + this.addAbility(new PlotAbility("{1}{B}{B}")); + } + + private PitilessCarnage(final PitilessCarnage card) { + super(card); + } + + @Override + public PitilessCarnage copy() { + return new PitilessCarnage(this); + } +} + +class PitilessCarnageEffect extends OneShotEffect { + + PitilessCarnageEffect() { + super(Outcome.Benefit); + staticText = "sacrifice any number of permanents you control, then draw that many cards"; + } + + private PitilessCarnageEffect(final PitilessCarnageEffect effect) { + super(effect); + } + + @Override + public PitilessCarnageEffect copy() { + return new PitilessCarnageEffect(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_CONTROLLED_PERMANENTS + ); + player.choose(outcome, target, source, game); + Set permanents = target + .getTargets() + .stream() + .map(game::getPermanent) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + if (permanents.isEmpty()) { + return false; + } + for (Permanent permanent : permanents) { + permanent.sacrifice(source, game); + } + player.drawCards(permanents.size(), source, game); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/p/PlanTheHeist.java b/Mage.Sets/src/mage/cards/p/PlanTheHeist.java new file mode 100644 index 00000000000..87f12f38e79 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PlanTheHeist.java @@ -0,0 +1,41 @@ +package mage.cards.p; + +import mage.abilities.condition.common.HellbentCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.keyword.SurveilEffect; +import mage.abilities.keyword.PlotAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class PlanTheHeist extends CardImpl { + + public PlanTheHeist(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{U}{U}"); + + // Surveil 3 if you have no cards in hand. Then draw three cards. + this.getSpellAbility().addEffect(new ConditionalOneShotEffect( + new SurveilEffect(3), HellbentCondition.instance, + "surveil 3 if you have no cards in hand" + )); + this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(3).concatBy("Then")); + + // Plot {3}{U} + this.addAbility(new PlotAbility("{3}{U}")); + } + + private PlanTheHeist(final PlanTheHeist card) { + super(card); + } + + @Override + public PlanTheHeist copy() { + return new PlanTheHeist(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PopularEntertainer.java b/Mage.Sets/src/mage/cards/p/PopularEntertainer.java index 7259b021591..3ff04e51477 100644 --- a/Mage.Sets/src/mage/cards/p/PopularEntertainer.java +++ b/Mage.Sets/src/mage/cards/p/PopularEntertainer.java @@ -12,13 +12,10 @@ import mage.filter.StaticFilters; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.permanent.ControllerIdPredicate; import mage.game.Game; -import mage.game.events.DamagedPlayerEvent; +import mage.game.events.DamagedBatchForOnePlayerEvent; import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; import mage.target.TargetPermanent; -import java.util.ArrayList; -import java.util.List; import java.util.UUID; /** @@ -51,8 +48,6 @@ public final class PopularEntertainer extends CardImpl { class PopularEntertainerAbility extends TriggeredAbilityImpl { - private final List damagedPlayerIds = new ArrayList<>(); - PopularEntertainerAbility() { super(Zone.BATTLEFIELD, new GoadTargetEffect(), false); } @@ -68,31 +63,26 @@ class PopularEntertainerAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PLAYER - || event.getType() == GameEvent.EventType.COMBAT_DAMAGE_STEP_POST; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PLAYER; } @Override public boolean checkTrigger(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.COMBAT_DAMAGE_STEP_POST) { - damagedPlayerIds.clear(); + DamagedBatchForOnePlayerEvent dEvent = (DamagedBatchForOnePlayerEvent) event; + + int damage = dEvent.getEvents() + .stream() + .filter(ev -> ev.getSourceId().equals(controllerId)) + .mapToInt(GameEvent::getAmount) + .sum(); + + if (!dEvent.isCombatDamage() || damage < 1){ return false; } - if (event.getType() != GameEvent.EventType.DAMAGED_PLAYER - || !((DamagedPlayerEvent) event).isCombatDamage()) { - return false; - } - Permanent creature = game.getPermanent(event.getSourceId()); - if (creature == null - || !creature.isControlledBy(getControllerId()) - || damagedPlayerIds.contains(event.getTargetId())) { - return false; - } - damagedPlayerIds.add(event.getTargetId()); FilterPermanent filter = new FilterCreaturePermanent( - "creature controlled by " + game.getPlayer(event.getTargetId()).getName() + "creature controlled by " + game.getPlayer(dEvent.getTargetId()).getName() ); - filter.add(new ControllerIdPredicate(event.getTargetId())); + filter.add(new ControllerIdPredicate(dEvent.getTargetId())); this.getTargets().clear(); this.addTarget(new TargetPermanent(filter)); return true; diff --git a/Mage.Sets/src/mage/cards/p/PowderGanger.java b/Mage.Sets/src/mage/cards/p/PowderGanger.java index a22a0e82ea7..5d52cd882b6 100644 --- a/Mage.Sets/src/mage/cards/p/PowderGanger.java +++ b/Mage.Sets/src/mage/cards/p/PowderGanger.java @@ -3,6 +3,7 @@ package mage.cards.p; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.common.DestroyTargetEffect; import mage.abilities.keyword.SquadAbility; import mage.cards.CardImpl; @@ -27,7 +28,7 @@ public final class PowderGanger extends CardImpl { this.toughness = new MageInt(2); // Squad {2} - this.addAbility(new SquadAbility()); + this.addAbility(new SquadAbility(new GenericManaCost(2))); // When Powder Ganger enters the battlefield, destroy up to one target artifact. Ability ability = new EntersBattlefieldTriggeredAbility(new DestroyTargetEffect()); diff --git a/Mage.Sets/src/mage/cards/p/PrairieDog.java b/Mage.Sets/src/mage/cards/p/PrairieDog.java new file mode 100644 index 00000000000..4cfb8830df2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PrairieDog.java @@ -0,0 +1,97 @@ +package mage.cards.p; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfEndStepTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.condition.common.HaventCastSpellFromHandThisTurnCondition; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.LifelinkAbility; +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.permanent.Permanent; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class PrairieDog extends CardImpl { + + public PrairieDog(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.subtype.add(SubType.SQUIRREL); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // At the beginning of your end step, if you haven't cast a spell from your hand this turn, put a +1/+1 counter on Prairie Dog. + this.addAbility(new BeginningOfEndStepTriggeredAbility( + Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.P1P1.createInstance()), + TargetController.YOU, HaventCastSpellFromHandThisTurnCondition.instance, false + ).addHint(HaventCastSpellFromHandThisTurnCondition.hint)); + + // {4}{W}: Until end of turn, if you would put one or more +1/+1 counters on a creature you control, put that many plus one +1/+1 counters on it instead. + this.addAbility(new SimpleActivatedAbility(new PrairieDogReplacementEffect(), new ManaCostsImpl<>("{4}{W}"))); + } + + private PrairieDog(final PrairieDog card) { + super(card); + } + + @Override + public PrairieDog copy() { + return new PrairieDog(this); + } +} + +class PrairieDogReplacementEffect extends ReplacementEffectImpl { + + PrairieDogReplacementEffect() { + super(Duration.EndOfTurn, Outcome.BoostCreature, false); + staticText = "Until end of turn, " + + "if you would put one or more +1/+1 counters on a creature you control, " + + "put that many plus one +1/+1 counters on it instead"; + } + + private PrairieDogReplacementEffect(final PrairieDogReplacementEffect effect) { + super(effect); + } + + @Override + public PrairieDogReplacementEffect copy() { + return new PrairieDogReplacementEffect(this); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + event.setAmountForCounters(CardUtil.overflowInc(event.getAmount(), 1), true); + return false; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ADD_COUNTERS; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + Permanent permanent = game.getPermanent(event.getTargetId()); + return event.getAmount() > 0 + && source.isControlledBy(event.getPlayerId()) + && permanent != null + && permanent.isCreature(game) + && permanent.isControlledBy(source.getControllerId()) + && event.getData().equals(CounterType.P1P1.getName()); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PrecognitionField.java b/Mage.Sets/src/mage/cards/p/PrecognitionField.java index 1641ba976c8..d233fcbb1e1 100644 --- a/Mage.Sets/src/mage/cards/p/PrecognitionField.java +++ b/Mage.Sets/src/mage/cards/p/PrecognitionField.java @@ -5,11 +5,10 @@ import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.common.ExileCardsFromTopOfLibraryControllerEffect; import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; -import mage.abilities.effects.common.continuous.PlayTheTopCardEffect; +import mage.abilities.effects.common.continuous.PlayFromTopOfLibraryEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.TargetController; import mage.filter.FilterCard; import mage.filter.predicate.Predicates; @@ -36,7 +35,7 @@ public final class PrecognitionField extends CardImpl { this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); // You may cast instant and sorcery spells from the top of your library. - this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(TargetController.YOU, filter, false))); + this.addAbility(new SimpleStaticAbility(new PlayFromTopOfLibraryEffect(filter))); // {3}: Exile the top card of your library. this.addAbility(new SimpleActivatedAbility(new ExileCardsFromTopOfLibraryControllerEffect(1), new GenericManaCost(3))); diff --git a/Mage.Sets/src/mage/cards/p/PricklyPair.java b/Mage.Sets/src/mage/cards/p/PricklyPair.java new file mode 100644 index 00000000000..22cb9585211 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PricklyPair.java @@ -0,0 +1,39 @@ +package mage.cards.p; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +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.MercenaryToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class PricklyPair extends CardImpl { + + public PricklyPair(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); + + this.subtype.add(SubType.PLANT); + this.subtype.add(SubType.MERCENARY); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // When Prickly Pair enters the battlefield, create a 1/1 red Mercenary creature token with "{T}: Target creature you control gets +1/+0 until end of turn. Activate only as a sorcery." + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new MercenaryToken()))); + } + + private PricklyPair(final PricklyPair card) { + super(card); + } + + @Override + public PricklyPair copy() { + return new PricklyPair(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PrisonersDilemma.java b/Mage.Sets/src/mage/cards/p/PrisonersDilemma.java new file mode 100644 index 00000000000..b931886e69f --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PrisonersDilemma.java @@ -0,0 +1,106 @@ +package mage.cards.p; + +import java.util.*; + +import mage.abilities.Ability; +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.game.Game; +import mage.players.Player; + +/** + * + * @author Skiwkr + */ +public final class PrisonersDilemma extends CardImpl { + + public PrisonersDilemma(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{R}{R}"); + + + // Each opponent secretly chooses silence or snitch, then the choices are revealed. If each opponent chose silence, Prisoner's Dilemma deals 4 damage to each of them. If each opponent chose snitch, Prisoner's Dilemma deals 8 damage to each of them. Otherwise, Prisoner's Dilemma deals 12 damage to each opponent who chose silence. + this.getSpellAbility().addEffect(new PrisonersDilemmaEffect()); + // Flashback {5}{R}{R} + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{5}{R}{R}"))); + + } + + private PrisonersDilemma(final PrisonersDilemma card) { + super(card); + } + + @Override + public PrisonersDilemma copy() { + return new PrisonersDilemma(this); + } +} + +class PrisonersDilemmaEffect extends OneShotEffect { + + PrisonersDilemmaEffect() { + super(Outcome.Benefit); + staticText = "Each opponent secretly chooses silence or snitch, then the choices are revealed. " + + "If each opponent chose silence, {this} deals 4 damage to each of them. " + + "If each opponent chose snitch, {this} deals 8 damage to each of them. "+ + "Otherwise, {this} deals 12 damage to each opponent who chose silence."; + } + + private PrisonersDilemmaEffect(final PrisonersDilemmaEffect effect) { + super(effect); + } + + @Override + public PrisonersDilemmaEffect copy() { + return new PrisonersDilemmaEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + List silence = new ArrayList<>(); + List snitch = new ArrayList<>(); + for (UUID playerId : game.getOpponents(source.getControllerId())) { + Player opponent = game.getPlayer(playerId); + if (opponent == null) { + continue; + } + boolean choseSilence = opponent.chooseUse(Outcome.Vote, "Choose silence or snitch", null, "Silence", "Snitch", source, game); + if (choseSilence) { + silence.add(opponent); + } else { + snitch.add(opponent); + } + } + + for (Player player : snitch) { + game.informPlayers(player.getName() + " chose snitch"); + } + + for (Player player : silence) { + game.informPlayers(player.getName() + " chose silence"); + } + + if (snitch.isEmpty()) { + for (Player player : silence) { + player.damage(4,source.getSourceId(),source,game); + } + } else if (silence.isEmpty()) { + for(Player player:snitch){ + player.damage(8,source.getSourceId(),source,game); + } + } else { + for(Player player : silence) { + player.damage(12, source.getSourceId(), source, game); + } + } + return true; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/p/ProjektorInspector.java b/Mage.Sets/src/mage/cards/p/ProjektorInspector.java new file mode 100644 index 00000000000..6379faa8fe5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/ProjektorInspector.java @@ -0,0 +1,62 @@ +package mage.cards.p; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.TurnedFaceUpAllTriggeredAbility; +import mage.abilities.effects.common.DrawDiscardControllerEffect; +import mage.abilities.meta.OrTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.filter.FilterPermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class ProjektorInspector extends CardImpl { + + private static final FilterPermanent filter1 = new FilterPermanent(SubType.DETECTIVE, "another Detective"); + private static final FilterPermanent filter2 = new FilterPermanent(SubType.DETECTIVE, "a Detective you control"); + + static { + filter1.add(AnotherPredicate.instance); + filter2.add(TargetController.YOU.getControllerPredicate()); + } + + public ProjektorInspector(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.DETECTIVE); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Whenever Projektor Inspector or another Detective enters the battlefield under your control and whenever a Detective you control is turned face up, you may draw a card. If you do, discard a card. + this.addAbility(new OrTriggeredAbility( + Zone.BATTLEFIELD, + new DrawDiscardControllerEffect(true), + false, + "Whenever {this} or another Detective enters the battlefield under your control and " + + "whenever a Detective you control is turned face up, ", + new EntersBattlefieldTriggeredAbility(null), + new EntersBattlefieldControlledTriggeredAbility(Zone.BATTLEFIELD, null, filter1, false), + new TurnedFaceUpAllTriggeredAbility(null, filter2) + )); + } + + private ProjektorInspector(final ProjektorInspector card) { + super(card); + } + + @Override + public ProjektorInspector copy() { + return new ProjektorInspector(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/ProsperityTycoon.java b/Mage.Sets/src/mage/cards/p/ProsperityTycoon.java new file mode 100644 index 00000000000..5e9cabe00c3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/ProsperityTycoon.java @@ -0,0 +1,56 @@ +package mage.cards.p; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.TapSourceEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +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.filter.StaticFilters; +import mage.game.permanent.token.MercenaryToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ProsperityTycoon extends CardImpl { + + public ProsperityTycoon(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.NOBLE); + this.power = new MageInt(4); + this.toughness = new MageInt(2); + + // When Prosperity Tycoon enters the battlefield, create a 1/1 red Mercenary creature token with "{T}: Target creature you control gets +1/+0 until end of turn. Activate only as a sorcery." + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new MercenaryToken()))); + + // {2}, Sacrifice a token: Prosperity Tycoon gains indestructible until end of turn. Tap it. + Ability ability = new SimpleActivatedAbility(new GainAbilitySourceEffect( + IndestructibleAbility.getInstance(), Duration.EndOfTurn + ), new GenericManaCost(2)); + ability.addCost(new SacrificeTargetCost(StaticFilters.FILTER_PERMANENT_TOKEN)); + ability.addEffect(new TapSourceEffect().setText("tap it")); + this.addAbility(ability); + } + + private ProsperityTycoon(final ProsperityTycoon card) { + super(card); + } + + @Override + public ProsperityTycoon copy() { + return new ProsperityTycoon(this); + } +} diff --git a/Mage.Sets/src/mage/cards/q/QuickDraw.java b/Mage.Sets/src/mage/cards/q/QuickDraw.java new file mode 100644 index 00000000000..419c06a0be3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/q/QuickDraw.java @@ -0,0 +1,84 @@ +package mage.cards.q; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.effects.common.continuous.LoseAbilityAllEffect; +import mage.abilities.keyword.DoubleStrikeAbility; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.ControllerIdPredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetOpponent; +import mage.target.targetpointer.SecondTargetPointer; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class QuickDraw extends CardImpl { + + public QuickDraw(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{R}"); + + // Target creature you control gets +1/+1 and gains first strike until end of turn. Creatures target opponent controls lose first strike and double strike until end of turn. + this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent()); + this.getSpellAbility().addTarget(new TargetOpponent()); + this.getSpellAbility().addEffect(new BoostTargetEffect(1, 1) + .setText("Target creature you control gets +1/+1")); + this.getSpellAbility().addEffect(new GainAbilityTargetEffect(FirstStrikeAbility.getInstance()) + .setText(" and gains first strike until end of turn.")); + this.getSpellAbility().addEffect(new QuickDrawEffect().setTargetPointer(new SecondTargetPointer())); + } + + private QuickDraw(final QuickDraw card) { + super(card); + } + + @Override + public QuickDraw copy() { + return new QuickDraw(this); + } +} + +class QuickDrawEffect extends OneShotEffect { + + QuickDrawEffect() { + super(Outcome.UnboostCreature); + staticText = "Creatures target opponent controls lose first strike and double strike until end of turn."; + } + + private QuickDrawEffect(final QuickDrawEffect effect) { + super(effect); + } + + @Override + public QuickDrawEffect copy() { + return new QuickDrawEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); + if (opponent == null) { + return false; + } + + FilterPermanent filter = new FilterCreaturePermanent(); + filter.add(new ControllerIdPredicate(opponent.getId())); + game.addEffect(new LoseAbilityAllEffect(FirstStrikeAbility.getInstance(), Duration.EndOfTurn, filter), source); + game.addEffect(new LoseAbilityAllEffect(DoubleStrikeAbility.getInstance(), Duration.EndOfTurn, filter), source); + return true; + } + +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/q/QuilledCharger.java b/Mage.Sets/src/mage/cards/q/QuilledCharger.java new file mode 100644 index 00000000000..1d41b2ae941 --- /dev/null +++ b/Mage.Sets/src/mage/cards/q/QuilledCharger.java @@ -0,0 +1,51 @@ +package mage.cards.q; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksWhileSaddledTriggeredAbility; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.keyword.MenaceAbility; +import mage.abilities.keyword.SaddleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class QuilledCharger extends CardImpl { + + public QuilledCharger(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}"); + + this.subtype.add(SubType.PORCUPINE); + this.subtype.add(SubType.MOUNT); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // Whenever Quilled Charger attacks while saddled, it gets +1/+2 and gains menace until end of turn. + Ability ability = new AttacksWhileSaddledTriggeredAbility( + new BoostSourceEffect(1, 2, Duration.EndOfTurn).setText("it gets +1/+2") + ); + ability.addEffect(new GainAbilitySourceEffect(new MenaceAbility(false)) + .setText("and gains menace until end of turn")); + this.addAbility(ability); + + // Saddle 2 + this.addAbility(new SaddleAbility(2)); + } + + private QuilledCharger(final QuilledCharger card) { + super(card); + } + + @Override + public QuilledCharger copy() { + return new QuilledCharger(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RadhaHeartOfKeld.java b/Mage.Sets/src/mage/cards/r/RadhaHeartOfKeld.java index daedc26b920..4d15b5f9f8a 100644 --- a/Mage.Sets/src/mage/cards/r/RadhaHeartOfKeld.java +++ b/Mage.Sets/src/mage/cards/r/RadhaHeartOfKeld.java @@ -11,7 +11,7 @@ import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; import mage.abilities.effects.common.continuous.BoostSourceEffect; import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; -import mage.abilities.effects.common.continuous.PlayTheTopCardEffect; +import mage.abilities.effects.common.continuous.PlayFromTopOfLibraryEffect; import mage.abilities.hint.common.MyTurnHint; import mage.abilities.keyword.FirstStrikeAbility; import mage.cards.CardImpl; @@ -48,7 +48,7 @@ public final class RadhaHeartOfKeld extends CardImpl { // You may look at the top card of your library any time, and you may play lands from the top of your library. LookAtTopCardOfLibraryAnyTimeEffect lookEffect = new LookAtTopCardOfLibraryAnyTimeEffect(); lookEffect.setText("You may look at the top card of your library any time"); - PlayTheTopCardEffect playEffect = new PlayTheTopCardEffect(TargetController.YOU, filter, false); + PlayFromTopOfLibraryEffect playEffect = new PlayFromTopOfLibraryEffect(filter); playEffect.setText(", and you may play lands from the top of your library"); SimpleStaticAbility lookAndPlayAbility = new SimpleStaticAbility(lookEffect); diff --git a/Mage.Sets/src/mage/cards/r/RailwayBrawler.java b/Mage.Sets/src/mage/cards/r/RailwayBrawler.java new file mode 100644 index 00000000000..2b3df21c89b --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RailwayBrawler.java @@ -0,0 +1,93 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.keyword.PlotAbility; +import mage.abilities.keyword.ReachAbility; +import mage.abilities.keyword.TrampleAbility; +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 java.util.UUID; + +/** + * @author Susucr + */ +public final class RailwayBrawler extends CardImpl { + + public RailwayBrawler(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}{G}"); + + this.subtype.add(SubType.RHINO); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Whenever another creature enters the battlefield under your control, put X +1/+1 counters on it, where X is its power. + this.addAbility(new EntersBattlefieldControlledTriggeredAbility( + Zone.BATTLEFIELD, + new RailwayBrawlerEffect(), + StaticFilters.FILTER_ANOTHER_CREATURE, + false, + SetTargetPointer.PERMANENT + )); + + // Plot {3}{G} + this.addAbility(new PlotAbility("{3}{G}")); + } + + private RailwayBrawler(final RailwayBrawler card) { + super(card); + } + + @Override + public RailwayBrawler copy() { + return new RailwayBrawler(this); + } +} + +class RailwayBrawlerEffect extends OneShotEffect { + + RailwayBrawlerEffect() { + super(Outcome.Benefit); + this.staticText = "put X +1/+1 counters on it, where X is its power"; + } + + private RailwayBrawlerEffect(final RailwayBrawlerEffect effect) { + super(effect); + } + + @Override + public RailwayBrawlerEffect copy() { + return new RailwayBrawlerEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent creature = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (creature == null) { + return false; + } + int pow = creature.getPower().getValue(); + if (pow <= 0) { + return false; + } + return new AddCountersTargetEffect(CounterType.P1P1.createInstance(pow)) + .setTargetPointer(getTargetPointer().copy()) + .apply(game, source); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RakdosJoinsUp.java b/Mage.Sets/src/mage/cards/r/RakdosJoinsUp.java new file mode 100644 index 00000000000..946e1119983 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RakdosJoinsUp.java @@ -0,0 +1,89 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.DiesCreatureTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldWithCounterTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetCardInYourGraveyard; +import mage.target.common.TargetOpponent; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RakdosJoinsUp extends CardImpl { + + public RakdosJoinsUp(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{B}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + + // When Rakdos Joins Up enters the battlefield, return target creature card from your graveyard to the battlefield with two additional +1/+1 counters on it. + Ability ability = new EntersBattlefieldTriggeredAbility( + new ReturnFromGraveyardToBattlefieldWithCounterTargetEffect(CounterType.P1P1.createInstance(2)) + ); + ability.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_CREATURE_YOUR_GRAVEYARD)); + this.addAbility(ability); + + // Whenever a legendary creature you control dies, Rakdos Joins Up deals damage equal to that creature's power to target opponent. + ability = new DiesCreatureTriggeredAbility( + new DamageTargetEffect(RakdosJoinsUpValue.instance), + false, StaticFilters.FILTER_CONTROLLED_CREATURE_LEGENDARY + ); + ability.addTarget(new TargetOpponent()); + this.addAbility(ability); + } + + private RakdosJoinsUp(final RakdosJoinsUp card) { + super(card); + } + + @Override + public RakdosJoinsUp copy() { + return new RakdosJoinsUp(this); + } +} + +enum RakdosJoinsUpValue implements DynamicValue { + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return Optional + .ofNullable(effect.getValue("creatureDied")) + .map(Permanent.class::cast) + .map(MageObject::getPower) + .map(MageInt::getValue) + .orElse(0); + } + + @Override + public RakdosJoinsUpValue copy() { + return this; + } + + @Override + public String getMessage() { + return "that creature's power"; + } + + @Override + public String toString() { + return "1"; + } +} diff --git a/Mage.Sets/src/mage/cards/r/RakdosTheMuscle.java b/Mage.Sets/src/mage/cards/r/RakdosTheMuscle.java new file mode 100644 index 00000000000..3ba2eb7a261 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RakdosTheMuscle.java @@ -0,0 +1,120 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.LimitedTimesPerTurnActivatedAbility; +import mage.abilities.common.SacrificePermanentTriggeredAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.TapSourceEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.IndestructibleAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPlayer; +import mage.util.CardUtil; + +import java.util.Set; +import java.util.UUID; + +/** + * @author Susucr + */ +public final class RakdosTheMuscle extends CardImpl { + + public RakdosTheMuscle(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}{B}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.DEMON); + this.subtype.add(SubType.MERCENARY); + this.power = new MageInt(6); + this.toughness = new MageInt(5); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Whenever you sacrifice another creature, exile cards equal to its mana value from the top of target player's library. Until your next end step, you may play those cards, and mana of any type can be spent to cast those spells. + Ability ability = new SacrificePermanentTriggeredAbility(Zone.BATTLEFIELD, + new RakdosTheMuscleEffect(), + StaticFilters.FILTER_CONTROLLED_ANOTHER_CREATURE, + TargetController.YOU, SetTargetPointer.PERMANENT, false + ); + ability.addTarget(new TargetPlayer()); + this.addAbility(ability); + + // Sacrifice another creature: Rakdos, the Muscle gains indestructible until end of turn. Tap it. Activate only once each turn. + ability = new LimitedTimesPerTurnActivatedAbility( + Zone.BATTLEFIELD, + new GainAbilitySourceEffect(IndestructibleAbility.getInstance(), Duration.EndOfTurn), + new SacrificeTargetCost(StaticFilters.FILTER_ANOTHER_CREATURE_YOU_CONTROL) + ); + ability.addEffect(new TapSourceEffect().setText("tap it")); + this.addAbility(ability); + } + + private RakdosTheMuscle(final RakdosTheMuscle card) { + super(card); + } + + @Override + public RakdosTheMuscle copy() { + return new RakdosTheMuscle(this); + } +} + +class RakdosTheMuscleEffect extends OneShotEffect { + + RakdosTheMuscleEffect() { + super(Outcome.Benefit); + staticText = "exile cards equal to its mana value from the top of target player's library. " + + "Until your next end step, you may play those cards, and mana of any type can be spent to cast those spells"; + } + + private RakdosTheMuscleEffect(final RakdosTheMuscleEffect effect) { + super(effect); + } + + @Override + public RakdosTheMuscleEffect copy() { + return new RakdosTheMuscleEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Player player = game.getPlayer(source.getFirstTarget()); + Permanent sacrificed = game.getPermanentOrLKIBattlefield(getTargetPointer().getFirst(game, source)); + if (player == null || controller == null || sacrificed == null) { + return false; + } + int amount = sacrificed.getManaValue(); + if (amount <= 0) { + return false; + } + + Set cards = player.getLibrary().getTopCards(game, amount); + if (cards.isEmpty()) { + return false; + } + player.moveCardsToExile(cards, source, game, true, CardUtil.getExileZoneId(game, source), CardUtil.getSourceName(game, source)); + // remove cards that could not be moved to exile + cards.removeIf(card -> !Zone.EXILED.equals(game.getState().getZone(card.getId()))); + for (Card card : cards) { + CardUtil.makeCardPlayable(game, source, card, Duration.UntilYourNextEndStep, true, controller.getId(), null); + } + return true; + } + +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/r/RakishCrew.java b/Mage.Sets/src/mage/cards/r/RakishCrew.java new file mode 100644 index 00000000000..0f7b86fc6b2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RakishCrew.java @@ -0,0 +1,50 @@ +package mage.cards.r; + +import mage.abilities.Ability; +import mage.abilities.common.DiesCreatureTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.LoseLifeOpponentsEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.mageobject.OutlawPredicate; +import mage.game.permanent.token.MercenaryToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RakishCrew extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledPermanent("an outlaw you control"); + + static { + filter.add(OutlawPredicate.instance); + } + + public RakishCrew(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{B}"); + + // When Rakish Crew enters the battlefield, create a 1/1 red Mercenary creature token with "{T}: Target creature you control gets +1/+0 until end of turn. Activate only as a sorcery." + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new MercenaryToken()))); + + // Whenever an outlaw you control dies, each opponent loses 1 life and you gain 1 life. + Ability ability = new DiesCreatureTriggeredAbility(new LoseLifeOpponentsEffect(1), false, filter); + ability.addEffect(new GainLifeEffect(1).concatBy("and")); + this.addAbility(ability); + } + + private RakishCrew(final RakishCrew card) { + super(card); + } + + @Override + public RakishCrew copy() { + return new RakishCrew(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RamblingPossum.java b/Mage.Sets/src/mage/cards/r/RamblingPossum.java new file mode 100644 index 00000000000..7694b337ed3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RamblingPossum.java @@ -0,0 +1,97 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksWhileSaddledTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.keyword.SaddleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.SaddledSourceThisTurnPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; + +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * @author TheElk801 + */ +public final class RamblingPossum extends CardImpl { + + public RamblingPossum(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.subtype.add(SubType.POSSUM); + this.subtype.add(SubType.MOUNT); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // 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. + Ability ability = new AttacksWhileSaddledTriggeredAbility(new BoostSourceEffect( + 1, 2, Duration.EndOfTurn, "it" + )); + ability.addEffect(new RamblingPossumEffect()); + this.addAbility(ability); + + // Saddle 1 + this.addAbility(new SaddleAbility(1)); + } + + private RamblingPossum(final RamblingPossum card) { + super(card); + } + + @Override + public RamblingPossum copy() { + return new RamblingPossum(this); + } +} + +class RamblingPossumEffect extends OneShotEffect { + + private static final FilterPermanent filter = new FilterCreaturePermanent("creatures that saddled it this turn"); + + static { + filter.add(SaddledSourceThisTurnPredicate.instance); + } + + RamblingPossumEffect() { + super(Outcome.Benefit); + staticText = "Then you may return any number of creatures that saddled it this turn to their owner's hand"; + } + + private RamblingPossumEffect(final RamblingPossumEffect effect) { + super(effect); + } + + @Override + public RamblingPossumEffect copy() { + return new RamblingPossumEffect(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); + player.choose(outcome, target, source, game); + Set permanents = target + .getTargets() + .stream() + .map(game::getPermanent) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + return player.moveCards(permanents, Zone.HAND, source, game); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RangerClass.java b/Mage.Sets/src/mage/cards/r/RangerClass.java index b570eb499b8..bdb5a16b0a5 100644 --- a/Mage.Sets/src/mage/cards/r/RangerClass.java +++ b/Mage.Sets/src/mage/cards/r/RangerClass.java @@ -7,7 +7,7 @@ import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.continuous.GainClassAbilitySourceEffect; import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; -import mage.abilities.effects.common.continuous.PlayTheTopCardEffect; +import mage.abilities.effects.common.continuous.PlayFromTopOfLibraryEffect; import mage.abilities.effects.common.counter.AddCountersTargetEffect; import mage.abilities.keyword.ClassLevelAbility; import mage.abilities.keyword.ClassReminderAbility; @@ -62,7 +62,7 @@ public final class RangerClass extends CardImpl { // You may cast creature spells from the top of your library. this.addAbility(new SimpleStaticAbility(new GainClassAbilitySourceEffect( - new PlayTheTopCardEffect(TargetController.YOU, filter, false), 3 + new PlayFromTopOfLibraryEffect(filter), 3 ))); } diff --git a/Mage.Sets/src/mage/cards/r/RattlebackApothecary.java b/Mage.Sets/src/mage/cards/r/RattlebackApothecary.java new file mode 100644 index 00000000000..faa601ab198 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RattlebackApothecary.java @@ -0,0 +1,85 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.CommittedCrimeTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.DeathtouchAbility; +import mage.abilities.keyword.LifelinkAbility; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RattlebackApothecary extends CardImpl { + + public RattlebackApothecary(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); + + this.subtype.add(SubType.GORGON); + this.subtype.add(SubType.WARLOCK); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Deathtouch + this.addAbility(DeathtouchAbility.getInstance()); + + // Whenever you commit a crime, target creature you control gains your choice of menace or lifelink until end of turn. + Ability ability = new CommittedCrimeTriggeredAbility(new RattlebackApothecaryEffect()); + ability.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(ability); + } + + private RattlebackApothecary(final RattlebackApothecary card) { + super(card); + } + + @Override + public RattlebackApothecary copy() { + return new RattlebackApothecary(this); + } +} + +class RattlebackApothecaryEffect extends OneShotEffect { + + RattlebackApothecaryEffect() { + super(Outcome.Benefit); + staticText = "target creature you control gains your choice of menace or lifelink until end of turn"; + } + + private RattlebackApothecaryEffect(final RattlebackApothecaryEffect effect) { + super(effect); + } + + @Override + public RattlebackApothecaryEffect copy() { + return new RattlebackApothecaryEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Ability ability = player.chooseUse( + outcome, "Choose menace or lifelink", null, + "Menace", "Lifelink", source, game + ) ? new MenaceAbility() : LifelinkAbility.getInstance(); + game.addEffect(new GainAbilityTargetEffect(ability, Duration.EndOfTurn) + .setTargetPointer(getTargetPointer().copy()), source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/r/RaucousEntertainer.java b/Mage.Sets/src/mage/cards/r/RaucousEntertainer.java new file mode 100644 index 00000000000..7efc4e84bb2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RaucousEntertainer.java @@ -0,0 +1,56 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.permanent.EnteredThisTurnPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RaucousEntertainer extends CardImpl { + + private static final FilterPermanent filter + = new FilterControlledCreaturePermanent("creature you control that entered the battlefield this turn"); + + static { + filter.add(EnteredThisTurnPredicate.instance); + } + + public RaucousEntertainer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); + + this.subtype.add(SubType.PLANT); + this.subtype.add(SubType.BARD); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // {1}, {T}: Put a +1/+1 counter on each creature you control that entered the battlefield this turn. + Ability ability = new SimpleActivatedAbility( + new AddCountersAllEffect(CounterType.P1P1.createInstance(), filter), new GenericManaCost(1) + ); + ability.addCost(new TapSourceCost()); + this.addAbility(ability); + } + + private RaucousEntertainer(final RaucousEntertainer card) { + super(card); + } + + @Override + public RaucousEntertainer copy() { + return new RaucousEntertainer(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RavenOfFellOmens.java b/Mage.Sets/src/mage/cards/r/RavenOfFellOmens.java new file mode 100644 index 00000000000..3f7bb48525c --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RavenOfFellOmens.java @@ -0,0 +1,45 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.CommittedCrimeTriggeredAbility; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.LoseLifeOpponentsEffect; +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 RavenOfFellOmens extends CardImpl { + + public RavenOfFellOmens(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}"); + + this.subtype.add(SubType.BIRD); + this.power = new MageInt(1); + this.toughness = new MageInt(2); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever you commit a crime, each opponent loses 1 life and you gain 1 life. This ability triggers only once each turn. + Ability ability = new CommittedCrimeTriggeredAbility(new LoseLifeOpponentsEffect(1)).setTriggersOnceEachTurn(true); + ability.addEffect(new GainLifeEffect(1).concatBy("and")); + this.addAbility(ability); + } + + private RavenOfFellOmens(final RavenOfFellOmens card) { + super(card); + } + + @Override + public RavenOfFellOmens copy() { + return new RavenOfFellOmens(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RazzleDazzler.java b/Mage.Sets/src/mage/cards/r/RazzleDazzler.java new file mode 100644 index 00000000000..ea856a4363b --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RazzleDazzler.java @@ -0,0 +1,44 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.CastSecondSpellTriggeredAbility; +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 java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RazzleDazzler extends CardImpl { + + public RazzleDazzler(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(1); + this.toughness = new MageInt(2); + + // Whenever you cast your second spell each turn, put a +1/+1 counter on Razzle-Dazzler. It can't be blocked this turn. + Ability ability = new CastSecondSpellTriggeredAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance())); + ability.addEffect(new CantBeBlockedSourceEffect(Duration.EndOfTurn).setText("it can't be blocked this turn")); + this.addAbility(ability); + } + + private RazzleDazzler(final RazzleDazzler card) { + super(card); + } + + @Override + public RazzleDazzler copy() { + return new RazzleDazzler(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/ReachForTheSky.java b/Mage.Sets/src/mage/cards/r/ReachForTheSky.java new file mode 100644 index 00000000000..6d5f3b68fc6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/ReachForTheSky.java @@ -0,0 +1,61 @@ +package mage.cards.r; + +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.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.continuous.BoostEnchantedEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.abilities.keyword.FlashAbility; +import mage.abilities.keyword.ReachAbility; +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 ReachForTheSky extends CardImpl { + + public ReachForTheSky(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{G}"); + + this.subtype.add(SubType.AURA); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // Enchant creature + TargetPermanent auraTarget = new TargetCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + this.addAbility(new EnchantAbility(auraTarget)); + + // Enchanted creature gets +3/+2 and has reach. + Ability ability = new SimpleStaticAbility(new BoostEnchantedEffect( + 3, 2, Duration.WhileOnBattlefield + )); + ability.addEffect(new GainAbilityAttachedEffect( + ReachAbility.getInstance(), AttachmentType.AURA + ).setText("and has reach")); + this.addAbility(ability); + + // When Reach for the Sky is put into a graveyard from the battlefield, draw a card. + this.addAbility(new PutIntoGraveFromBattlefieldSourceTriggeredAbility(new DrawCardSourceControllerEffect(1))); + } + + private ReachForTheSky(final ReachForTheSky card) { + super(card); + } + + @Override + public ReachForTheSky copy() { + return new ReachForTheSky(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/Realmwalker.java b/Mage.Sets/src/mage/cards/r/Realmwalker.java index 75fc454b9a8..a746a2743d4 100644 --- a/Mage.Sets/src/mage/cards/r/Realmwalker.java +++ b/Mage.Sets/src/mage/cards/r/Realmwalker.java @@ -5,7 +5,7 @@ import mage.abilities.common.AsEntersBattlefieldAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.ChooseCreatureTypeEffect; import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; -import mage.abilities.effects.common.continuous.PlayTheTopCardEffect; +import mage.abilities.effects.common.continuous.PlayFromTopOfLibraryEffect; import mage.abilities.keyword.ChangelingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -46,7 +46,7 @@ public final class Realmwalker extends CardImpl { this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); // You may cast creature spells of the chosen type from the top of your library. - this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(TargetController.YOU, filter, false))); + this.addAbility(new SimpleStaticAbility(new PlayFromTopOfLibraryEffect(filter))); } private Realmwalker(final Realmwalker card) { diff --git a/Mage.Sets/src/mage/cards/r/RecklessLackey.java b/Mage.Sets/src/mage/cards/r/RecklessLackey.java new file mode 100644 index 00000000000..0cf3655ffdc --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RecklessLackey.java @@ -0,0 +1,56 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +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.SubType; +import mage.game.permanent.token.TreasureToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RecklessLackey extends CardImpl { + + public RecklessLackey(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{R}"); + + this.subtype.add(SubType.GOBLIN); + this.subtype.add(SubType.PIRATE); + this.power = new MageInt(1); + this.toughness = new MageInt(2); + + // First strike + this.addAbility(FirstStrikeAbility.getInstance()); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // {2}{R}, Sacrifice Reckless Lackey: Draw a card and create a Treasure token. + Ability ability = new SimpleActivatedAbility( + new DrawCardSourceControllerEffect(1), new ManaCostsImpl<>("{2}{R}") + ); + ability.addCost(new SacrificeSourceCost()); + ability.addEffect(new CreateTokenEffect(new TreasureToken()).concatBy("and")); + this.addAbility(ability); + } + + private RecklessLackey(final RecklessLackey card) { + super(card); + } + + @Override + public RecklessLackey copy() { + return new RecklessLackey(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RedrockSentinel.java b/Mage.Sets/src/mage/cards/r/RedrockSentinel.java new file mode 100644 index 00000000000..c5b6055c167 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RedrockSentinel.java @@ -0,0 +1,54 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.keyword.DefenderAbility; +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 RedrockSentinel extends CardImpl { + + public RedrockSentinel(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{3}"); + + this.subtype.add(SubType.GOLEM); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // Defender + this.addAbility(DefenderAbility.getInstance()); + + // {2}, {T}, Sacrifice a land: Draw a card and create a Treasure token. + Ability ability = new SimpleActivatedAbility( + new DrawCardSourceControllerEffect(1), new GenericManaCost(2) + ); + ability.addCost(new TapSourceCost()); + ability.addCost(new SacrificeTargetCost(StaticFilters.FILTER_CONTROLLED_LAND_SHORT_TEXT)); + ability.addEffect(new CreateTokenEffect(new TreasureToken()).concatBy("and")); + this.addAbility(ability); + } + + private RedrockSentinel(final RedrockSentinel card) { + super(card); + } + + @Override + public RedrockSentinel copy() { + return new RedrockSentinel(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/Repercussion.java b/Mage.Sets/src/mage/cards/r/Repercussion.java index 16d073cc375..2f7c4e51c30 100644 --- a/Mage.Sets/src/mage/cards/r/Repercussion.java +++ b/Mage.Sets/src/mage/cards/r/Repercussion.java @@ -54,7 +54,7 @@ class RepercussionTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT; } @Override diff --git a/Mage.Sets/src/mage/cards/r/RequisitionRaid.java b/Mage.Sets/src/mage/cards/r/RequisitionRaid.java new file mode 100644 index 00000000000..0dd899a0324 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RequisitionRaid.java @@ -0,0 +1,86 @@ +package mage.cards.r; + +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.keyword.SpreeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPlayer; +import mage.target.common.TargetArtifactPermanent; +import mage.target.common.TargetEnchantmentPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RequisitionRaid extends CardImpl { + + public RequisitionRaid(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{W}"); + + // Spree + this.addAbility(new SpreeAbility(this)); + + // + {1} -- Destroy target artifact. + this.getSpellAbility().addEffect(new DestroyTargetEffect()); + this.getSpellAbility().addTarget(new TargetArtifactPermanent()); + this.getSpellAbility().withFirstModeCost(new GenericManaCost(1)); + + // + {1} -- Destroy target enchantment. + this.getSpellAbility().addMode(new Mode(new DestroyTargetEffect()) + .addTarget(new TargetEnchantmentPermanent()) + .withCost(new GenericManaCost(1))); + + // + {1} -- Put a +1/+1 counter on each creature target player controls. + this.getSpellAbility().addMode(new Mode(new RequisitionRaidEffect()) + .addTarget(new TargetPlayer()) + .withCost(new GenericManaCost(1))); + } + + private RequisitionRaid(final RequisitionRaid card) { + super(card); + } + + @Override + public RequisitionRaid copy() { + return new RequisitionRaid(this); + } +} + +class RequisitionRaidEffect extends OneShotEffect { + + RequisitionRaidEffect() { + super(Outcome.Benefit); + staticText = "put a +1/+1 counter on each creature target player controls"; + } + + private RequisitionRaidEffect(final RequisitionRaidEffect effect) { + super(effect); + } + + @Override + public RequisitionRaidEffect copy() { + return new RequisitionRaidEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + for (Permanent permanent : game.getBattlefield().getActivePermanents( + StaticFilters.FILTER_CONTROLLED_CREATURE, + getTargetPointer().getFirst(game, source), source, game + )) { + permanent.addCounters(CounterType.P1P1.createInstance(), source, game); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/r/ResilientRoadrunner.java b/Mage.Sets/src/mage/cards/r/ResilientRoadrunner.java new file mode 100644 index 00000000000..c4168b7d3c0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/ResilientRoadrunner.java @@ -0,0 +1,63 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.combat.CantBeBlockedByCreaturesSourceEffect; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.ProtectionAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.AbilityPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ResilientRoadrunner extends CardImpl { + + private static final FilterCard filter = new FilterCard("Coyotes"); + private static final FilterCreaturePermanent filter2 + = new FilterCreaturePermanent("except by creatures with haste"); + + static { + filter.add(SubType.COYOTE.getPredicate()); + filter2.add(Predicates.not(new AbilityPredicate(HasteAbility.class))); + } + + public ResilientRoadrunner(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}"); + + this.subtype.add(SubType.BIRD); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // Protection from Coyotes + this.addAbility(new ProtectionAbility(filter)); + + // {3}: Resilient Roadrunner can't be blocked this turn except by creatures with haste. + this.addAbility(new SimpleActivatedAbility( + new CantBeBlockedByCreaturesSourceEffect(filter2, Duration.EndOfTurn), new GenericManaCost(3) + )); + } + + private ResilientRoadrunner(final ResilientRoadrunner card) { + super(card); + } + + @Override + public ResilientRoadrunner copy() { + return new ResilientRoadrunner(this); + } +} +// meep meep diff --git a/Mage.Sets/src/mage/cards/r/ReturnTheFavor.java b/Mage.Sets/src/mage/cards/r/ReturnTheFavor.java new file mode 100644 index 00000000000..4061f712e46 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/ReturnTheFavor.java @@ -0,0 +1,69 @@ +package mage.cards.r; + +import mage.abilities.Mode; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.ChooseNewTargetsTargetEffect; +import mage.abilities.effects.common.CopyTargetStackAbilityEffect; +import mage.abilities.keyword.SpreeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.FilterStackObject; +import mage.filter.predicate.Predicate; +import mage.filter.predicate.other.NumberOfTargetsPredicate; +import mage.game.Game; +import mage.game.stack.Spell; +import mage.game.stack.StackObject; +import mage.target.TargetStackObject; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ReturnTheFavor extends CardImpl { + + private static final FilterStackObject filter + = new FilterStackObject("instant spell, sorcery spell, activated ability, or triggered ability"); + private static final FilterStackObject filter2 = new FilterStackObject("spell or ability with a single target"); + + static { + filter.add(ReturnTheFavorPredicate.instance); + filter2.add(new NumberOfTargetsPredicate(1)); + } + + public ReturnTheFavor(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{R}{R}"); + + // Spree + this.addAbility(new SpreeAbility(this)); + + // + {1} -- Copy target instant spell, sorcery spell, activated ability, or triggered ability. You may choose new targets for the copy. + this.getSpellAbility().addEffect(new CopyTargetStackAbilityEffect()); + this.getSpellAbility().addTarget(new TargetStackObject(filter)); + this.getSpellAbility().withFirstModeCost(new GenericManaCost(1)); + + // + {1} -- Change the target of target spell or ability with a single target. + this.getSpellAbility().addMode(new Mode(new ChooseNewTargetsTargetEffect(true, true)) + .addTarget(new TargetStackObject(filter2)) + .withCost(new GenericManaCost(1))); + } + + private ReturnTheFavor(final ReturnTheFavor card) { + super(card); + } + + @Override + public ReturnTheFavor copy() { + return new ReturnTheFavor(this); + } +} + +enum ReturnTheFavorPredicate implements Predicate { + instance; + + @Override + public boolean apply(StackObject input, Game game) { + return !(input instanceof Spell) || input.isInstantOrSorcery(game); + } +} diff --git a/Mage.Sets/src/mage/cards/r/ReverseThePolarity.java b/Mage.Sets/src/mage/cards/r/ReverseThePolarity.java new file mode 100644 index 00000000000..e26c6857785 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/ReverseThePolarity.java @@ -0,0 +1,80 @@ +package mage.cards.r; + +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.combat.CantBeBlockedAllEffect; +import mage.abilities.effects.common.continuous.SwitchPowerToughnessAllEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.stack.Spell; +import mage.game.stack.StackObject; + +import java.util.LinkedList; +import java.util.List; +import java.util.UUID; + +/** + * + * @author notgreat + */ +public final class ReverseThePolarity extends CardImpl { + + public ReverseThePolarity(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}{U}"); + + // Choose one -- + // * Counter all other spells. + this.getSpellAbility().addEffect(new ReverseThePolarityCounterAllEffect()); + // * Switch each creature's power and toughness until end of turn. + this.getSpellAbility().addMode(new Mode(new SwitchPowerToughnessAllEffect(Duration.EndOfTurn))); + // * Creatures can't be blocked this turn. + this.getSpellAbility().addMode(new Mode(new CantBeBlockedAllEffect(StaticFilters.FILTER_PERMANENT_CREATURES, Duration.EndOfTurn))); + } + + private ReverseThePolarity(final ReverseThePolarity card) { + super(card); + } + + @Override + public ReverseThePolarity copy() { + return new ReverseThePolarity(this); + } +} +//Based on Counterflux/Swift Silence +class ReverseThePolarityCounterAllEffect extends OneShotEffect { + + ReverseThePolarityCounterAllEffect() { + super(Outcome.Detriment); + staticText = "Counter all other spells."; + } + + private ReverseThePolarityCounterAllEffect(final ReverseThePolarityCounterAllEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + List spellsToCounter = new LinkedList<>(); + for (StackObject stackObject : game.getStack()) { + if (stackObject instanceof Spell && !stackObject.getId().equals(source.getSourceObject(game).getId())) { + spellsToCounter.add((Spell) stackObject); + } + } + for (Spell spell : spellsToCounter) { + game.getStack().counter(spell.getId(), source, game); + } + return true; + } + + @Override + public ReverseThePolarityCounterAllEffect copy() { + return new ReverseThePolarityCounterAllEffect(this); + } + +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/r/RictusRobber.java b/Mage.Sets/src/mage/cards/r/RictusRobber.java new file mode 100644 index 00000000000..1e38fd4fa14 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RictusRobber.java @@ -0,0 +1,50 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.common.MorbidCondition; +import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.hint.common.MorbidHint; +import mage.abilities.keyword.PlotAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.game.permanent.token.ZombieRogueToken; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class RictusRobber extends CardImpl { + + public RictusRobber(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}"); + + this.subtype.add(SubType.ZOMBIE); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // When Rictus Robber enters the battlefield, if a creature died this turn, create a 2/2 blue and black Zombie Rogue creature token. + this.addAbility(new ConditionalInterveningIfTriggeredAbility( + new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new ZombieRogueToken())), + MorbidCondition.instance, + "When {this} enters the battlefield, if a creature died this turn, create a 2/2 blue and black Zombie Rogue creature token." + ).addHint(MorbidHint.instance)); + + // Plot {2}{B} + this.addAbility(new PlotAbility("{2}{B}")); + } + + private RictusRobber(final RictusRobber card) { + super(card); + } + + @Override + public RictusRobber copy() { + return new RictusRobber(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RikuOfManyPaths.java b/Mage.Sets/src/mage/cards/r/RikuOfManyPaths.java new file mode 100644 index 00000000000..7145f8eb16b --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RikuOfManyPaths.java @@ -0,0 +1,93 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.Mode; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.ExileTopXMayPlayUntilEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.TrampleAbility; +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.permanent.token.BlueBirdToken; +import mage.game.stack.Spell; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RikuOfManyPaths extends CardImpl { + + public RikuOfManyPaths(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}{U}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Whenever you cast a modal spell, choose up to X, where X is the number of times you chose a mode for that spell -- + // * Exile the top card of your library. Until the end of your next turn, you may play it. + // * Put a +1/+1 counter on Riku of Many Paths. It gains trample until end of turn. + // * Create a 1/1 blue Bird creature token with flying. + this.addAbility(new RikuOfManyPathsTriggeredAbility()); + } + + private RikuOfManyPaths(final RikuOfManyPaths card) { + super(card); + } + + @Override + public RikuOfManyPaths copy() { + return new RikuOfManyPaths(this); + } +} + +class RikuOfManyPathsTriggeredAbility extends TriggeredAbilityImpl { + + RikuOfManyPathsTriggeredAbility() { + super(Zone.BATTLEFIELD, new ExileTopXMayPlayUntilEffect(1, Duration.UntilEndOfYourNextTurn) + .withTextOptions("it", false)); + this.setTriggerPhrase("Whenever you cast a modal spell, "); + this.getModes().setChooseText("choose up to X, where X is the number of times you chose a mode for that spell —"); + this.addMode(new Mode(new AddCountersSourceEffect(CounterType.P1P1.createInstance())) + .addEffect(new GainAbilitySourceEffect( + TrampleAbility.getInstance(), Duration.EndOfTurn + ).setText("it gains trample until end of turn"))); + this.addMode(new Mode(new CreateTokenEffect(new BlueBirdToken()))); + this.getModes().setMinModes(0); + } + + private RikuOfManyPathsTriggeredAbility(final RikuOfManyPathsTriggeredAbility ability) { + super(ability); + } + + @Override + public RikuOfManyPathsTriggeredAbility copy() { + return new RikuOfManyPathsTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.SPELL_CAST; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + Spell spell = game.getSpell(event.getTargetId()); + if (spell == null + || !spell.isControlledBy(getControllerId()) + || spell.getSpellAbility().getModes().size() < 2) { + return false; + } + this.getModes().setMaxModes(spell.getSpellAbility().getModes().getSelectedModes().size()); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/r/RiseOfTheVarmints.java b/Mage.Sets/src/mage/cards/r/RiseOfTheVarmints.java new file mode 100644 index 00000000000..e74d6392cca --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RiseOfTheVarmints.java @@ -0,0 +1,42 @@ +package mage.cards.r; + +import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.PlotAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; +import mage.game.permanent.token.VarmintToken; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class RiseOfTheVarmints extends CardImpl { + + public RiseOfTheVarmints(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{G}"); + + + // Create X 2/1 green Varmint creature tokens, where X is the number of creature cards in your graveyard. + this.getSpellAbility().addEffect(new CreateTokenEffect( + new VarmintToken(), + new CardsInControllerGraveyardCount(StaticFilters.FILTER_CARD_CREATURE) + ).setText("Create X 2/1 green Varmint creature tokens, where X is the number of creature cards in your graveyard.")); + + // Plot {2}{G} + this.addAbility(new PlotAbility("{2}{G}")); + + } + + private RiseOfTheVarmints(final RiseOfTheVarmints card) { + super(card); + } + + @Override + public RiseOfTheVarmints copy() { + return new RiseOfTheVarmints(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RisonaAsariCommander.java b/Mage.Sets/src/mage/cards/r/RisonaAsariCommander.java index f1b2debb6fb..910cfe40ec0 100644 --- a/Mage.Sets/src/mage/cards/r/RisonaAsariCommander.java +++ b/Mage.Sets/src/mage/cards/r/RisonaAsariCommander.java @@ -18,7 +18,7 @@ import mage.constants.CardType; import mage.constants.Zone; import mage.counters.CounterType; import mage.game.Game; -import mage.game.events.DamagedBatchEvent; +import mage.game.events.DamagedBatchForPlayersEvent; import mage.game.events.DamagedEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; @@ -85,19 +85,11 @@ class RisonaAsariCommanderTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { - if (controllerId == null) { - return false; - } - if (!(event instanceof DamagedBatchEvent)) { - return false; - } - DamagedBatchEvent batchEvent = (DamagedBatchEvent) event; - for (DamagedEvent damageEvent : batchEvent.getEvents()) { - if (damageEvent.isCombatDamage() && controllerId.equals(damageEvent.getTargetId())) { - return true; - } - } - return false; + return ((DamagedBatchForPlayersEvent) event) + .getEvents() + .stream() + .filter(DamagedEvent::isCombatDamage) + .anyMatch(e -> e.getTargetId().equals(getControllerId())); } } diff --git a/Mage.Sets/src/mage/cards/r/RiteOfPassage.java b/Mage.Sets/src/mage/cards/r/RiteOfPassage.java index 2ac19b2afce..2242d1c3d19 100644 --- a/Mage.Sets/src/mage/cards/r/RiteOfPassage.java +++ b/Mage.Sets/src/mage/cards/r/RiteOfPassage.java @@ -61,7 +61,7 @@ class RiteOfPassageTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT; } @Override @@ -69,7 +69,7 @@ class RiteOfPassageTriggeredAbility extends TriggeredAbilityImpl { UUID targetId = event.getTargetId(); Permanent permanent = game.getPermanent(targetId); if (permanent != null && StaticFilters.FILTER_CONTROLLED_CREATURE.match(permanent, getControllerId(), this, game)) { - getEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game)); + getEffects().setTargetPointer(new FixedTarget(targetId, game)); return true; } return false; diff --git a/Mage.Sets/src/mage/cards/r/RivazOfTheClaw.java b/Mage.Sets/src/mage/cards/r/RivazOfTheClaw.java index b95474add03..6bc09194c35 100644 --- a/Mage.Sets/src/mage/cards/r/RivazOfTheClaw.java +++ b/Mage.Sets/src/mage/cards/r/RivazOfTheClaw.java @@ -1,7 +1,7 @@ package mage.cards.r; import mage.MageInt; -import mage.abilities.common.CastFromGraveyardOnceStaticAbility; +import mage.abilities.common.CastFromGraveyardOnceEachTurnAbility; import mage.abilities.common.DiesSourceTriggeredAbility; import mage.abilities.common.SpellCastControllerTriggeredAbility; import mage.abilities.effects.common.ExileSourceEffect; @@ -24,7 +24,7 @@ import java.util.UUID; public final class RivazOfTheClaw extends CardImpl { private static final FilterCreatureSpell manaAbilityFilter = new FilterCreatureSpell("Dragon creature spells"); - private static final FilterCreatureCard staticAbilityFilter = new FilterCreatureCard(); + private static final FilterCreatureCard staticAbilityFilter = new FilterCreatureCard("a Dragon creature spell"); private static final FilterCreatureSpell spellCastFilter = new FilterCreatureSpell("a Dragon creature spell from your graveyard"); static { @@ -50,7 +50,7 @@ public final class RivazOfTheClaw extends CardImpl { this.addAbility(new ConditionalAnyColorManaAbility(2, new ConditionalSpellManaBuilder(manaAbilityFilter))); // Once during each of your turns, you may cast a Dragon creature spell from your graveyard. - this.addAbility(new CastFromGraveyardOnceStaticAbility(staticAbilityFilter, "Once during each of your turns, you may cast a Dragon creature spell from your graveyard")); + this.addAbility(new CastFromGraveyardOnceEachTurnAbility(staticAbilityFilter)); // Whenever you cast a Dragon creature spell from your graveyard, it gains "When this creature dies, exile it." this.addAbility(new SpellCastControllerTriggeredAbility( diff --git a/Mage.Sets/src/mage/cards/r/RobobrainWarMind.java b/Mage.Sets/src/mage/cards/r/RobobrainWarMind.java new file mode 100644 index 00000000000..5040637c5a4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RobobrainWarMind.java @@ -0,0 +1,63 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.common.PayEnergyCost; +import mage.abilities.dynamicvalue.common.CardsInControllerHandCount; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.continuous.SetBasePowerSourceEffect; +import mage.abilities.effects.common.counter.GetEnergyCountersControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterArtifactCreaturePermanent; + +import java.util.UUID; + +/** + * + * @author justinjohnson14 + */ +public final class RobobrainWarMind extends CardImpl { + + private static final FilterPermanent filter = new FilterArtifactCreaturePermanent(); + + static { + filter.add(TargetController.YOU.getControllerPredicate()); + } + + public RobobrainWarMind(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{3}{U}"); + + this.subtype.add(SubType.ROBOT); + this.power = new MageInt(0); + this.toughness = new MageInt(5); + + // Robobrain War Mind's power is equal to the number of cards in your hand. + this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerSourceEffect(CardsInControllerHandCount.instance))); + + // When Robobrain War Mind enters the battlefield, you get an amount of {E} equal to the number of artifact creatures you control. + this.addAbility(new EntersBattlefieldTriggeredAbility(new GetEnergyCountersControllerEffect(new PermanentsOnBattlefieldCount(filter)) + .setText("you get an amount of {E} equal to the number of artifact creatures you control"))); + + // Whenever Robobrain War Mind attacks, you may pay {E}{E}{E}. If you do, draw a card. + this.addAbility(new AttacksTriggeredAbility(new DoIfCostPaid(new DrawCardSourceControllerEffect(1), new PayEnergyCost(3)))); + } + + private RobobrainWarMind(final RobobrainWarMind card) { + super(card); + } + + @Override + public RobobrainWarMind copy() { + return new RobobrainWarMind(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RodeoPyromancers.java b/Mage.Sets/src/mage/cards/r/RodeoPyromancers.java new file mode 100644 index 00000000000..d684a2caa79 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RodeoPyromancers.java @@ -0,0 +1,74 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.Mana; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.mana.BasicManaEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.watchers.common.SpellsCastWatcher; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class RodeoPyromancers extends CardImpl { + + public RodeoPyromancers(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.MERCENARY); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // Whenever you cast your first spell each turn, add {R}{R}. + this.addAbility(new RodeoPyromancersTriggeredAbility()); + } + + private RodeoPyromancers(final RodeoPyromancers card) { + super(card); + } + + @Override + public RodeoPyromancers copy() { + return new RodeoPyromancers(this); + } +} + +class RodeoPyromancersTriggeredAbility extends TriggeredAbilityImpl { + + RodeoPyromancersTriggeredAbility() { + super(Zone.BATTLEFIELD, new BasicManaEffect(Mana.RedMana(2))); + setTriggerPhrase("Whenever you cast your first spell each turn, "); + } + + private RodeoPyromancersTriggeredAbility(final RodeoPyromancersTriggeredAbility ability) { + super(ability); + } + + @Override + public RodeoPyromancersTriggeredAbility copy() { + return new RodeoPyromancersTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.SPELL_CAST; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (event.getPlayerId().equals(this.getControllerId())) { + SpellsCastWatcher watcher = game.getState().getWatcher(SpellsCastWatcher.class); + return watcher != null && watcher.getSpellsCastThisTurn(this.getControllerId()).size() == 1; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/r/RooftopAssassin.java b/Mage.Sets/src/mage/cards/r/RooftopAssassin.java new file mode 100644 index 00000000000..12000e5d9ea --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RooftopAssassin.java @@ -0,0 +1,64 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.keyword.FlashAbility; +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.filter.FilterPermanent; +import mage.filter.common.FilterOpponentsCreaturePermanent; +import mage.filter.predicate.permanent.WasDealtDamageThisTurnPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RooftopAssassin extends CardImpl { + + private static final FilterPermanent filter + = new FilterOpponentsCreaturePermanent("creature an opponent controls that was dealt damage this turn"); + + static { + filter.add(WasDealtDamageThisTurnPredicate.instance); + } + + public RooftopAssassin(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}"); + + this.subtype.add(SubType.VAMPIRE); + this.subtype.add(SubType.ASSASSIN); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // When Rooftop Assassin enters the battlefield, destroy target creature an opponent controls that was dealt damage this turn. + Ability ability = new EntersBattlefieldTriggeredAbility(new DestroyTargetEffect()); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + } + + private RooftopAssassin(final RooftopAssassin card) { + super(card); + } + + @Override + public RooftopAssassin copy() { + return new RooftopAssassin(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RoryWilliams.java b/Mage.Sets/src/mage/cards/r/RoryWilliams.java new file mode 100644 index 00000000000..d59c32fc647 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RoryWilliams.java @@ -0,0 +1,82 @@ +package mage.cards.r; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.CastSourceTriggeredAbility; +import mage.abilities.effects.common.ExileSpellWithTimeCountersEffect; +import mage.abilities.effects.keyword.InvestigateEffect; +import mage.abilities.keyword.*; +import mage.constants.*; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.stack.Spell; + +/** + * + * @author Skiwkr + */ +public final class RoryWilliams extends CardImpl { + + public RoryWilliams(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W}{U}"); + + 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); + + // Partner with Amy Pond + this.addAbility(new PartnerWithAbility("Amy Pond")); + + // First strike + this.addAbility(FirstStrikeAbility.getInstance()); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // The Last Centurion -- When you cast this spell from anywhere other than exile, exile it with three time counters on it. It gains suspend. Then investigate. + Ability ability = new RoryWilliamsTriggeredAbility(new ExileSpellWithTimeCountersEffect(3, true) + .setText("exile it with three time counters on it. It gains suspend")); + ability.addEffect(new InvestigateEffect(1).concatBy("Then")); + ability.withFlavorWord("The Last Centurion"); + this.addAbility(ability); + } + + private RoryWilliams(final RoryWilliams card) { + super(card); + } + + @Override + public RoryWilliams copy() { + return new RoryWilliams(this); + } +} + +class RoryWilliamsTriggeredAbility extends CastSourceTriggeredAbility { + + RoryWilliamsTriggeredAbility(Effect effect) { + super(effect, false); + setRuleAtTheTop(true); + setTriggerPhrase("When you cast this spell from anywhere other than exile, "); + } + + private RoryWilliamsTriggeredAbility(final RoryWilliamsTriggeredAbility ability) { + super(ability); + } + + @Override + public RoryWilliamsTriggeredAbility copy() { + return new RoryWilliamsTriggeredAbility(this); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return super.checkTrigger(event,game) + && !((Spell) game.getObject(sourceId)).getFromZone().equals(Zone.EXILED); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RoseNoble.java b/Mage.Sets/src/mage/cards/r/RoseNoble.java index f431d8c06e0..aeb0c54adc2 100644 --- a/Mage.Sets/src/mage/cards/r/RoseNoble.java +++ b/Mage.Sets/src/mage/cards/r/RoseNoble.java @@ -44,7 +44,7 @@ public final class RoseNoble extends CardImpl { this.toughness = new MageInt(3); // Ward {2} - this.addAbility(new WardAbility(new ManaCostsImpl<>("{2}"))); + this.addAbility(new WardAbility(new ManaCostsImpl<>("{2}"), false)); // Whenever you cast a Doctor spell or creature spell with doctor's companion, draw a card. this.addAbility(new SpellCastControllerTriggeredAbility( diff --git a/Mage.Sets/src/mage/cards/r/RoxanneStarfallSavant.java b/Mage.Sets/src/mage/cards/r/RoxanneStarfallSavant.java new file mode 100644 index 00000000000..bb76b754a33 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RoxanneStarfallSavant.java @@ -0,0 +1,60 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility; +import mage.abilities.common.TapForManaAllTriggeredManaAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.mana.AddManaOfAnyTypeProducedEffect; +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.FilterControlledArtifactPermanent; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.game.permanent.token.MeteoriteToken; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class RoxanneStarfallSavant extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledArtifactPermanent("you tap an artifact token"); + + static { + filter.add(TokenPredicate.TRUE); + } + + public RoxanneStarfallSavant(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.CAT); + this.subtype.add(SubType.DRUID); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // Whenever Roxanne, Starfall Savant enters the battlefield or attacks, create a tapped colorless artifact token named Meteorite with "When Meteorite enters the battlefield, it deals 2 damage to any target" and "{T}: Add one mana of any color." + this.addAbility(new EntersBattlefieldOrAttacksSourceTriggeredAbility(new CreateTokenEffect(new MeteoriteToken(), 1, true))); + + // Whenever you tap an artifact token for mana, add one mana of any type that artifact token produced. + this.addAbility(new TapForManaAllTriggeredManaAbility( + new AddManaOfAnyTypeProducedEffect() + .setText("add one mana of any type that artifact token produced"), + filter, SetTargetPointer.PERMANENT + )); + } + + private RoxanneStarfallSavant(final RoxanneStarfallSavant card) { + super(card); + } + + @Override + public RoxanneStarfallSavant copy() { + return new RoxanneStarfallSavant(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/Rumbleweed.java b/Mage.Sets/src/mage/cards/r/Rumbleweed.java new file mode 100644 index 00000000000..5484fdada10 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/Rumbleweed.java @@ -0,0 +1,76 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.abilities.keyword.ReachAbility; +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.Zone; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class Rumbleweed extends CardImpl { + + private static final DynamicValue xValue = new CardsInControllerGraveyardCount(StaticFilters.FILTER_CARD_LAND, 1); + private static final Hint hint = new ValueHint("Lands in your graveyard", xValue); + + public Rumbleweed(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{10}{G}"); + + this.subtype.add(SubType.PLANT); + this.subtype.add(SubType.ELEMENTAL); + this.power = new MageInt(8); + this.toughness = new MageInt(8); + + // This spell costs {1} less to cast for each land card in your graveyard. + this.addAbility(new SimpleStaticAbility( + Zone.ALL, new SpellCostReductionForEachSourceEffect(1, xValue) + ).addHint(hint)); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // When Rumbleweed enters the battlefield, other creatures you control get +3/+3 and gain trample until end of turn. + Ability ability = new EntersBattlefieldTriggeredAbility(new BoostControlledEffect( + 3, 3, Duration.EndOfTurn, true + ).setText("other creatures you control get +3/+3")); + ability.addEffect(new GainAbilityControlledEffect( + TrampleAbility.getInstance(), Duration.EndOfTurn, + StaticFilters.FILTER_PERMANENT_CREATURE, true + ).setText("and gain trample until end of turn")); + this.addAbility(ability); + } + + private Rumbleweed(final Rumbleweed card) { + super(card); + } + + @Override + public Rumbleweed copy() { + return new Rumbleweed(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RushOfDread.java b/Mage.Sets/src/mage/cards/r/RushOfDread.java new file mode 100644 index 00000000000..074d3885d9c --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RushOfDread.java @@ -0,0 +1,118 @@ +package mage.cards.r; + +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.LoseHalfLifeTargetEffect; +import mage.abilities.effects.common.SacrificeEffect; +import mage.abilities.keyword.SpreeAbility; +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.players.Player; +import mage.target.common.TargetOpponent; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RushOfDread extends CardImpl { + + public RushOfDread(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{B}{B}"); + + // Spree + this.addAbility(new SpreeAbility(this)); + + // + {1} -- Target opponent sacrifices half the creatures they control, rounded up. + this.getSpellAbility().addEffect(new RushOfDreadSacrificeEffect()); + this.getSpellAbility().addTarget(new TargetOpponent()); + this.getSpellAbility().withFirstModeCost(new GenericManaCost(1)); + + // + {2} -- Target opponent discards half the cards in their hand, rounded up. + this.getSpellAbility().addMode(new Mode(new RushOfDreadDiscardEffect()) + .addTarget(new TargetOpponent()) + .withCost(new GenericManaCost(2))); + + // + {2} -- Target opponent loses half their life, rounded up. + this.getSpellAbility().addMode(new Mode(new LoseHalfLifeTargetEffect()) + .addTarget(new TargetOpponent()) + .withCost(new GenericManaCost(2))); + } + + private RushOfDread(final RushOfDread card) { + super(card); + } + + @Override + public RushOfDread copy() { + return new RushOfDread(this); + } +} + +class RushOfDreadSacrificeEffect extends OneShotEffect { + + RushOfDreadSacrificeEffect() { + super(Outcome.Benefit); + staticText = "target opponent sacrifices half the creatures they control, rounded up"; + } + + private RushOfDreadSacrificeEffect(final RushOfDreadSacrificeEffect effect) { + super(effect); + } + + @Override + public RushOfDreadSacrificeEffect copy() { + return new RushOfDreadSacrificeEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); + if (opponent == null) { + return false; + } + int creatures = game + .getBattlefield() + .count(StaticFilters.FILTER_CONTROLLED_CREATURES, opponent.getId(), source, game); + int toSac = Math.floorDiv(creatures, 2) + creatures % 2; + return new SacrificeEffect(StaticFilters.FILTER_PERMANENT_CREATURE, toSac, "") + .setTargetPointer(new FixedTarget(opponent.getId())) + .apply(game, source); + } +} + +class RushOfDreadDiscardEffect extends OneShotEffect { + + RushOfDreadDiscardEffect() { + super(Outcome.Benefit); + staticText = "target opponent discards half the cards in their hand, rounded up"; + } + + private RushOfDreadDiscardEffect(final RushOfDreadDiscardEffect effect) { + super(effect); + } + + @Override + public RushOfDreadDiscardEffect copy() { + return new RushOfDreadDiscardEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); + return opponent != null + && !opponent.getHand().isEmpty() + && opponent.discard( + Math.floorDiv(opponent.getHand().size(), 2) + + opponent.getHand().size() % 2, + false, false, source, game + ).size() > 0; + } +} diff --git a/Mage.Sets/src/mage/cards/r/RushingRiver.java b/Mage.Sets/src/mage/cards/r/RushingRiver.java index 120a95cf86c..d51b76db9f8 100644 --- a/Mage.Sets/src/mage/cards/r/RushingRiver.java +++ b/Mage.Sets/src/mage/cards/r/RushingRiver.java @@ -3,20 +3,16 @@ package mage.cards.r; import mage.abilities.Ability; import mage.abilities.condition.common.KickedCondition; import mage.abilities.costs.common.SacrificeTargetCost; -import mage.abilities.decorator.ConditionalOneShotEffect; -import mage.abilities.effects.Effect; import mage.abilities.effects.common.ReturnToHandTargetEffect; import mage.abilities.keyword.KickerAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.filter.StaticFilters; -import mage.filter.common.FilterControlledLandPermanent; import mage.game.Game; -import mage.target.common.TargetControlledPermanent; import mage.target.common.TargetNonlandPermanent; import mage.target.targetadjustment.TargetAdjuster; -import mage.target.targetpointer.SecondTargetPointer; +import mage.target.targetpointer.EachTargetPointer; import java.util.UUID; @@ -28,19 +24,14 @@ public final class RushingRiver extends CardImpl { public RushingRiver(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{U}"); - // Kicker-Sacrifice a land. this.addAbility(new KickerAbility(new SacrificeTargetCost(StaticFilters.FILTER_LAND))); // Return target nonland permanent to its owner's hand. If Rushing River was kicked, return another target nonland permanent to its owner's hand. - this.getSpellAbility().addEffect(new ReturnToHandTargetEffect()); - Effect effect = new ConditionalOneShotEffect( - new ReturnToHandTargetEffect(), - KickedCondition.ONCE, - "if this spell was kicked, return another target nonland permanent to its owner's hand"); - effect.setTargetPointer(new SecondTargetPointer()); - this.getSpellAbility().addEffect(effect); - this.getSpellAbility().addTarget(new TargetNonlandPermanent().withChooseHint("nonland to return")); + this.getSpellAbility().addEffect(new ReturnToHandTargetEffect() + .setTargetPointer(new EachTargetPointer()) + .setText("Return target nonland permanent to its owner's hand. " + + "If this spell was kicked, return another target nonland permanent to its owner's hand")); this.getSpellAbility().setTargetAdjuster(RushingRiverAdjuster.instance); } @@ -59,9 +50,9 @@ enum RushingRiverAdjuster implements TargetAdjuster { @Override public void adjustTargets(Ability ability, Game game) { - if (KickedCondition.ONCE.apply(game, ability)) { - ability.getTargets().clear(); - ability.addTarget(new TargetNonlandPermanent(2)); - } + ability.getTargets().clear(); + ability.addTarget(new TargetNonlandPermanent( + KickedCondition.ONCE.apply(game, ability) ? 2 : 1 + )); } } diff --git a/Mage.Sets/src/mage/cards/r/RustlerRampage.java b/Mage.Sets/src/mage/cards/r/RustlerRampage.java new file mode 100644 index 00000000000..16dff102d58 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RustlerRampage.java @@ -0,0 +1,80 @@ +package mage.cards.r; + +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.DoubleStrikeAbility; +import mage.abilities.keyword.SpreeAbility; +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.TargetPlayer; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RustlerRampage extends CardImpl { + + public RustlerRampage(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{W}"); + + // Spree + this.addAbility(new SpreeAbility(this)); + + // + {1} -- Untap all creatures target player controls. + this.getSpellAbility().addEffect(new RustlerRampageEffect()); + this.getSpellAbility().addTarget(new TargetPlayer()); + this.getSpellAbility().withFirstModeCost(new GenericManaCost(1)); + + // + {1} -- Target creature gains double strike until end of turn. + this.getSpellAbility().addMode(new Mode(new GainAbilityTargetEffect(DoubleStrikeAbility.getInstance())) + .addTarget(new TargetCreaturePermanent()) + .withCost(new GenericManaCost(1))); + } + + private RustlerRampage(final RustlerRampage card) { + super(card); + } + + @Override + public RustlerRampage copy() { + return new RustlerRampage(this); + } +} + +class RustlerRampageEffect extends OneShotEffect { + + RustlerRampageEffect() { + super(Outcome.Benefit); + staticText = "untap all creatures target player controls"; + } + + private RustlerRampageEffect(final RustlerRampageEffect effect) { + super(effect); + } + + @Override + public RustlerRampageEffect copy() { + return new RustlerRampageEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + for (Permanent permanent : game.getBattlefield().getActivePermanents( + StaticFilters.FILTER_CONTROLLED_CREATURE, + getTargetPointer().getFirst(game, source), source, game + )) { + permanent.untap(game); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/r/RuthlessLawbringer.java b/Mage.Sets/src/mage/cards/r/RuthlessLawbringer.java new file mode 100644 index 00000000000..e172172bd66 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RuthlessLawbringer.java @@ -0,0 +1,48 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.effects.common.DoWhenCostPaid; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.target.common.TargetNonlandPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RuthlessLawbringer extends CardImpl { + + public RuthlessLawbringer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}{B}"); + + this.subtype.add(SubType.VAMPIRE); + this.subtype.add(SubType.ASSASSIN); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // When Ruthless Lawbringer enters the battlefield, you may sacrifice another creature. When you do, destroy target nonland permanent. + ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility(new DestroyTargetEffect(), false); + ability.addTarget(new TargetNonlandPermanent()); + this.addAbility(new EntersBattlefieldTriggeredAbility(new DoWhenCostPaid( + ability, new SacrificeTargetCost(StaticFilters.FILTER_ANOTHER_CREATURE), + "Sacrifice another creature?" + ))); + } + + private RuthlessLawbringer(final RuthlessLawbringer card) { + super(card); + } + + @Override + public RuthlessLawbringer copy() { + return new RuthlessLawbringer(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RuthlessRadrat.java b/Mage.Sets/src/mage/cards/r/RuthlessRadrat.java new file mode 100644 index 00000000000..7b4c174cb2a --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RuthlessRadrat.java @@ -0,0 +1,43 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.costs.common.ExileFromGraveCost; +import mage.abilities.keyword.MenaceAbility; +import mage.abilities.keyword.SquadAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author notgreat + */ +public final class RuthlessRadrat extends CardImpl { + + public RuthlessRadrat(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}{B}"); + + this.subtype.add(SubType.RAT); + this.subtype.add(SubType.MUTANT); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Squad -- Exile four cards from your graveyard. + this.addAbility(new SquadAbility(new ExileFromGraveCost(new TargetCardInYourGraveyard(4)))); + + // Menace + this.addAbility(new MenaceAbility()); + } + + private RuthlessRadrat(final RuthlessRadrat card) { + super(card); + } + + @Override + public RuthlessRadrat copy() { + return new RuthlessRadrat(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SageOfTheBeyond.java b/Mage.Sets/src/mage/cards/s/SageOfTheBeyond.java index 4317d671c37..a30c497f268 100644 --- a/Mage.Sets/src/mage/cards/s/SageOfTheBeyond.java +++ b/Mage.Sets/src/mage/cards/s/SageOfTheBeyond.java @@ -5,17 +5,12 @@ import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.cost.SpellsCostReductionControllerEffect; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.ForetellAbility; -import mage.cards.Card; 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.predicate.ObjectSourcePlayer; -import mage.filter.predicate.ObjectSourcePlayerPredicate; -import mage.game.Game; -import mage.game.stack.Spell; +import mage.filter.predicate.other.SpellCastFromAnywhereOtherThanHand; import java.util.UUID; @@ -27,7 +22,7 @@ public final class SageOfTheBeyond extends CardImpl { private static final FilterCard filter = new FilterCard(); static { - filter.add(SageOfTheBeyondPredicate.instance); + filter.add(SpellCastFromAnywhereOtherThanHand.instance); } public SageOfTheBeyond(UUID ownerId, CardSetInfo setInfo) { @@ -57,19 +52,4 @@ public final class SageOfTheBeyond extends CardImpl { public SageOfTheBeyond copy() { return new SageOfTheBeyond(this); } -} - -enum SageOfTheBeyondPredicate implements ObjectSourcePlayerPredicate { - instance; - - @Override - public boolean apply(ObjectSourcePlayer input, Game game) { - if (input.getObject() instanceof Spell) { - return !input.getObject().isOwnedBy(input.getPlayerId()) - || !Zone.HAND.match(((Spell) input.getObject()).getFromZone()); - } else { - return !input.getObject().isOwnedBy(input.getPlayerId()) - || !Zone.HAND.match(game.getState().getZone(input.getObject().getId())); - } - } -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/s/SandScout.java b/Mage.Sets/src/mage/cards/s/SandScout.java new file mode 100644 index 00000000000..71d14bd9fa4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SandScout.java @@ -0,0 +1,65 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.PutCardIntoGraveFromAnywhereAllTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.OpponentControlsMoreCondition; +import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.TargetController; +import mage.filter.FilterCard; +import mage.filter.StaticFilters; +import mage.game.permanent.token.HazezonTamarSandWarriorToken; +import mage.target.common.TargetCardInLibrary; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class SandScout extends CardImpl { + + private static final FilterCard filter = new FilterCard("a Desert card"); + + static { + filter.add(SubType.DESERT.getPredicate()); + } + + private static final Condition condition = new OpponentControlsMoreCondition(StaticFilters.FILTER_LAND); + + public SandScout(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SCOUT); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // When Sand Scout enters the battlefield, if an opponent controls more lands than you, search your library for a Desert card, put it onto the battlefield tapped, then shuffle. + this.addAbility(new ConditionalInterveningIfTriggeredAbility(new EntersBattlefieldTriggeredAbility( + new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(filter), true)), + condition, "When {this} enters the battlefield, if an opponent controls more lands than you, " + + "search your library for a Desert card, put it onto the battlefield tapped, then shuffle." + )); + + // Whenever one or more land cards are put into your graveyard from anywhere, create a 1/1 red, green, and white Sand Warrior creature token. This ability triggers only once each turn. + this.addAbility(new PutCardIntoGraveFromAnywhereAllTriggeredAbility( + new CreateTokenEffect(new HazezonTamarSandWarriorToken()), false, StaticFilters.FILTER_CARD_LAND, TargetController.YOU + ).setTriggerPhrase("Whenever one or more land cards are put into your graveyard from anywhere, ").setTriggersOnceEachTurn(true)); + } + + private SandScout(final SandScout card) { + super(card); + } + + @Override + public SandScout copy() { + return new SandScout(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SandstormSalvager.java b/Mage.Sets/src/mage/cards/s/SandstormSalvager.java new file mode 100644 index 00000000000..d73ee881d12 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SandstormSalvager.java @@ -0,0 +1,65 @@ +package mage.cards.s; + +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.continuous.GainAbilityAllEffect; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.game.permanent.token.GolemToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SandstormSalvager extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledCreaturePermanent("creature token you control"); + + static { + filter.add(TokenPredicate.TRUE); + } + + public SandstormSalvager(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ARTIFICER); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // When Sandstorm Salvager enters the battlefield, create a 3/3 colorless Golem artifact creature token. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new GolemToken()))); + + // {2}, {T}: Put a +1/+1 counter on each creature token you control. They gain trample until end of turn. + Ability ability = new SimpleActivatedAbility( + new AddCountersAllEffect(CounterType.P1P1.createInstance(), filter), new GenericManaCost(2) + ); + ability.addCost(new TapSourceCost()); + ability.addEffect(new GainAbilityAllEffect(TrampleAbility.getInstance(), Duration.EndOfTurn, filter)); + this.addAbility(ability); + } + + private SandstormSalvager(final SandstormSalvager card) { + super(card); + } + + @Override + public SandstormSalvager copy() { + return new SandstormSalvager(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SandstormVerge.java b/Mage.Sets/src/mage/cards/s/SandstormVerge.java new file mode 100644 index 00000000000..4df070dbf0f --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SandstormVerge.java @@ -0,0 +1,48 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.combat.CantBlockTargetEffect; +import mage.abilities.mana.ColorlessManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SandstormVerge extends CardImpl { + + public SandstormVerge(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + this.subtype.add(SubType.DESERT); + + // {T}: Add {C}. + this.addAbility(new ColorlessManaAbility()); + + // {3}, {T}: Target creature can't block this turn. Activate only as a sorcery. + Ability ability = new ActivateAsSorceryActivatedAbility( + new CantBlockTargetEffect(Duration.EndOfTurn), new GenericManaCost(3) + ); + ability.addCost(new TapSourceCost()); + ability.addTarget(new TargetCreaturePermanent()); + this.addAbility(ability); + } + + private SandstormVerge(final SandstormVerge card) { + super(card); + } + + @Override + public SandstormVerge copy() { + return new SandstormVerge(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SatoruTheInfiltrator.java b/Mage.Sets/src/mage/cards/s/SatoruTheInfiltrator.java new file mode 100644 index 00000000000..a772eb1f613 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SatoruTheInfiltrator.java @@ -0,0 +1,111 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +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.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeBatchEvent; +import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.Permanent; +import mage.game.permanent.PermanentToken; +import mage.game.stack.Spell; + +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * @author Susucr + */ +public final class SatoruTheInfiltrator extends CardImpl { + + public SatoruTheInfiltrator(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{U}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.NINJA); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Menace + this.addAbility(new MenaceAbility()); + + // Whenever Satoru, the Infiltrator and/or one or more other nontoken creatures enter the battlefield under your control, if none of them were cast or no mana was spent to cast them, draw a card. + this.addAbility(new SatoruTheInfiltratorTriggeredAbility()); + } + + private SatoruTheInfiltrator(final SatoruTheInfiltrator card) { + super(card); + } + + @Override + public SatoruTheInfiltrator copy() { + return new SatoruTheInfiltrator(this); + } +} + +class SatoruTheInfiltratorTriggeredAbility extends TriggeredAbilityImpl { + + public SatoruTheInfiltratorTriggeredAbility() { + super(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1), false); + this.setTriggerPhrase("Whenever {this} and/or one or more other nontoken creatures " + + "enter the battlefield under your control, if none of them were cast or no mana was spent to cast them, "); + } + + protected SatoruTheInfiltratorTriggeredAbility(final SatoruTheInfiltratorTriggeredAbility ability) { + super(ability); + } + + @Override + public SatoruTheInfiltratorTriggeredAbility copy() { + return new SatoruTheInfiltratorTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ZONE_CHANGE_BATCH; + } + + // event is GameEvent.EventType.ENTERS_THE_BATTLEFIELD + @Override + public boolean checkTrigger(GameEvent event, Game game) { + ZoneChangeBatchEvent zEvent = (ZoneChangeBatchEvent) event; + List moved = zEvent.getEvents() + .stream() + .filter(e -> e.getToZone() == Zone.BATTLEFIELD) // Keep only to the battlefield + .filter(e -> { + Permanent permanent = e.getTarget(); + if (permanent == null) { + return false; + } + return permanent.isControlledBy(getControllerId()) // under your control + && (permanent.getId().equals(getSourceId()) // {this} + || (permanent.isCreature(game) && !(permanent instanceof PermanentToken)) // other nontoken Creature + ); + }) + .collect(Collectors.toList()); + if (moved.isEmpty()) { + return false; + } + // At this point, we have at least one event matching + // "Whenever {this} and/or one or more other nontoken creatures enter the battlefield under your control" + // Now to check that none were cast using mana + for (ZoneChangeEvent zce : moved) { + Spell spell = game.getSpellOrLKIStack(zce.getTargetId()); + if (spell != null && spell.getStackAbility().getManaCostsToPay().getUsedManaToPay().count() > 0) { + return false; // found one that was cast using mana, so no triggering. + } + } + return true; // all relevant permanents passed the spell mana check, so triggering. + } +} diff --git a/Mage.Sets/src/mage/cards/s/SatyrFiredancer.java b/Mage.Sets/src/mage/cards/s/SatyrFiredancer.java index 42a3e84d51c..95ad9e5b92d 100644 --- a/Mage.Sets/src/mage/cards/s/SatyrFiredancer.java +++ b/Mage.Sets/src/mage/cards/s/SatyrFiredancer.java @@ -51,9 +51,9 @@ public final class SatyrFiredancer extends CardImpl { class SatyrFiredancerTriggeredAbility extends TriggeredAbilityImpl { - public SatyrFiredancerTriggeredAbility() { + SatyrFiredancerTriggeredAbility() { super(Zone.BATTLEFIELD, new SatyrFiredancerDamageEffect(), false); - targetAdjuster = SatyrFiredancerAdjuster.instance; + this.setTargetAdjuster(SatyrFiredancerAdjuster.instance); setTriggerPhrase("Whenever an instant or sorcery spell you control deals damage to an opponent, "); } diff --git a/Mage.Sets/src/mage/cards/s/SavvyTrader.java b/Mage.Sets/src/mage/cards/s/SavvyTrader.java new file mode 100644 index 00000000000..49a7ef374c6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SavvyTrader.java @@ -0,0 +1,109 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.asthought.PlayFromNotOwnHandZoneTargetEffect; +import mage.abilities.effects.common.cost.SpellsCostReductionControllerEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.StaticFilters; +import mage.filter.predicate.other.SpellCastFromAnywhereOtherThanHand; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetCardInYourGraveyard; +import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class SavvyTrader extends CardImpl { + + private static final FilterCard filter = new FilterCard(); + + static { + filter.add(SpellCastFromAnywhereOtherThanHand.instance); + } + + public SavvyTrader(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.CITIZEN); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // When Savvy Trader enters the battlefield, exile target permanent card from your graveyard. You may play that card for as long as it remains exiled. + Ability ability = new EntersBattlefieldTriggeredAbility(new SavvyTraderEffect()); + ability.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_PERMANENT)); + this.addAbility(ability); + + // Spells you cast from anywhere other than your hand cost {1} less to cast. + this.addAbility(new SimpleStaticAbility(new SpellsCostReductionControllerEffect(filter, 1) + .setText("Spells you cast from anywhere other than your hand cost {1} less to cast."))); + } + + private SavvyTrader(final SavvyTrader card) { + super(card); + } + + @Override + public SavvyTrader copy() { + return new SavvyTrader(this); + } +} + +class SavvyTraderEffect extends OneShotEffect { + + SavvyTraderEffect() { + super(Outcome.DrawCard); + staticText = "exile target permanent card from your graveyard. " + + "You may play that card for as long as it remains exiled"; + } + + private SavvyTraderEffect(final SavvyTraderEffect effect) { + super(effect); + } + + @Override + public SavvyTraderEffect copy() { + return new SavvyTraderEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(source.getSourceId()); + Card card = game.getCard(source.getFirstTarget()); + if (sourcePermanent == null || controller == null || card == null) { + return false; + } + // One single exile zone per player is enough for this effect. source does not matter. + // TODO: have a rework to group together in that same exile zone all cards in exile that + // - are not linked to any other ability (like return on some condition / be counted by some effet) + // - can be played by a single player until end of game + // On a more broad subject, there is a bunch of improvements we could do to exile zone management. + String keyForPlayer = "Shared::EndOfGame::PlayerMayPlay=" + controller.getId(); + UUID exileId = CardUtil.getExileZoneId(keyForPlayer, game); + String exileName = controller.getName() + " may play for as long as cards remains exiled"; + if (controller.moveCardsToExile(card, source, game, true, exileId, exileName)) { + ContinuousEffect effect = new PlayFromNotOwnHandZoneTargetEffect(Duration.EndOfGame); + effect.setTargetPointer(new FixedTarget(card, game)); + game.addEffect(effect, source); + } + return true; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/s/ScalestormSummoner.java b/Mage.Sets/src/mage/cards/s/ScalestormSummoner.java new file mode 100644 index 00000000000..dba1d2a3b2c --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/ScalestormSummoner.java @@ -0,0 +1,49 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.condition.common.FerociousCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.hint.common.FerociousHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.game.permanent.token.Dinosaur31Token; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class ScalestormSummoner extends CardImpl { + + public ScalestormSummoner(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARLOCK); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Whenever Scalestorm Summoner attacks, create a 3/1 red Dinosaur creature token if you control a creature with power 4 or greater. + this.addAbility(new AttacksTriggeredAbility( + new ConditionalOneShotEffect( + new CreateTokenEffect(new Dinosaur31Token()), + FerociousCondition.instance, + "create a 3/1 red Dinosaur creature token " + + "if you control a creature with power 4 or greater" + ) + ).addHint(FerociousHint.instance)); + } + + private ScalestormSummoner(final ScalestormSummoner card) { + super(card); + } + + @Override + public ScalestormSummoner copy() { + return new ScalestormSummoner(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/ScholarOfTheLostTrove.java b/Mage.Sets/src/mage/cards/s/ScholarOfTheLostTrove.java index aadfea87a73..df43362984d 100644 --- a/Mage.Sets/src/mage/cards/s/ScholarOfTheLostTrove.java +++ b/Mage.Sets/src/mage/cards/s/ScholarOfTheLostTrove.java @@ -70,7 +70,7 @@ class ScholarOfTheLostTroveEffect extends OneShotEffect { ScholarOfTheLostTroveEffect() { super(Outcome.PlayForFree); this.staticText = "you may cast target instant, sorcery, or artifact card from your graveyard without paying its mana cost. " + - "If an instant or sorcery spell cast this way would be put into your graveyard this turn, exile it instead"; + "If an instant or sorcery spell cast this way would be put into your graveyard, exile it instead"; } private ScholarOfTheLostTroveEffect(final ScholarOfTheLostTroveEffect effect) { diff --git a/Mage.Sets/src/mage/cards/s/ScorchingShot.java b/Mage.Sets/src/mage/cards/s/ScorchingShot.java new file mode 100644 index 00000000000..0bd307362b3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/ScorchingShot.java @@ -0,0 +1,32 @@ +package mage.cards.s; + +import mage.abilities.effects.common.DamageTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ScorchingShot extends CardImpl { + + public ScorchingShot(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{R}{R}"); + + // Scorching Shot deals 5 damage to target creature. + this.getSpellAbility().addEffect(new DamageTargetEffect(5)); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + } + + private ScorchingShot(final ScorchingShot card) { + super(card); + } + + @Override + public ScorchingShot copy() { + return new ScorchingShot(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SecuritronSquadron.java b/Mage.Sets/src/mage/cards/s/SecuritronSquadron.java new file mode 100644 index 00000000000..a59105a1946 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SecuritronSquadron.java @@ -0,0 +1,64 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.keyword.SquadAbility; +import mage.abilities.keyword.VigilanceAbility; +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.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.TokenPredicate; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class SecuritronSquadron extends CardImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent("creature token"); + + static { + filter.add(TokenPredicate.TRUE); + } + + public SecuritronSquadron(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{1}{W}"); + + this.subtype.add(SubType.ROBOT); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Squad {3} + this.addAbility(new SquadAbility(new GenericManaCost(3))); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Whenever a creature token enters the battlefield under your control, put a +1/+1 counter on it. + this.addAbility(new EntersBattlefieldControlledTriggeredAbility( + Zone.BATTLEFIELD, + new AddCountersTargetEffect(CounterType.P1P1.createInstance()).setText("put a +1/+1 counter on it"), + filter, + false, + SetTargetPointer.PERMANENT + )); + } + + private SecuritronSquadron(final SecuritronSquadron card) { + super(card); + } + + @Override + public SecuritronSquadron copy() { + return new SecuritronSquadron(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SeizeTheSecrets.java b/Mage.Sets/src/mage/cards/s/SeizeTheSecrets.java new file mode 100644 index 00000000000..a1abd85fdc6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SeizeTheSecrets.java @@ -0,0 +1,45 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.CommittedCrimeCondition; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +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.watchers.common.CommittedCrimeWatcher; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class SeizeTheSecrets extends CardImpl { + + public SeizeTheSecrets(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{U}"); + + // This spell costs {1} less to cast if you've committed a crime this turn. + Ability ability = new SimpleStaticAbility( + Zone.ALL, + new SpellCostReductionSourceEffect(1, CommittedCrimeCondition.instance) + ); + ability.setRuleAtTheTop(true); + ability.addHint(CommittedCrimeCondition.getHint()); + this.addAbility(ability, new CommittedCrimeWatcher()); + + // Draw two cards. + this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(2)); + } + + private SeizeTheSecrets(final SeizeTheSecrets card) { + super(card); + } + + @Override + public SeizeTheSecrets copy() { + return new SeizeTheSecrets(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SelvalaEagerTrailblazer.java b/Mage.Sets/src/mage/cards/s/SelvalaEagerTrailblazer.java new file mode 100644 index 00000000000..b9f7a821809 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SelvalaEagerTrailblazer.java @@ -0,0 +1,102 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.MageObject; +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.hint.common.CovenHint; +import mage.abilities.keyword.VigilanceAbility; +import mage.abilities.mana.DynamicManaAbility; +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 mage.game.permanent.token.MercenaryToken; + +import java.util.Objects; +import java.util.UUID; + +/** + * @author Susucr + */ +public final class SelvalaEagerTrailblazer extends CardImpl { + + public SelvalaEagerTrailblazer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.ELF); + this.subtype.add(SubType.SCOUT); + this.power = new MageInt(4); + this.toughness = new MageInt(5); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Whenever you cast a creature spell, create a 1/1 red Mercenary creature token with "{T}: Target creature you control gets +1/+0 until end of turn. Activate only as a sorcery." + this.addAbility(new SpellCastControllerTriggeredAbility( + new CreateTokenEffect(new MercenaryToken()), + StaticFilters.FILTER_SPELL_A_CREATURE, false + )); + + // {T}: Choose a color. Add one mana of that color for each different power among creatures you control. + this.addAbility(new DynamicManaAbility( + Mana.AnyMana(1), SelvalaEagerTrailblazerValue.instance, new TapSourceCost(), + "Choose a color. Add one mana of that color for each different power among creatures you control", + true + ).addHint(CovenHint.instance)); + } + + private SelvalaEagerTrailblazer(final SelvalaEagerTrailblazer card) { + super(card); + } + + @Override + public SelvalaEagerTrailblazer copy() { + return new SelvalaEagerTrailblazer(this); + } +} + +enum SelvalaEagerTrailblazerValue implements DynamicValue { + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return game + .getBattlefield() + .getActivePermanents( + StaticFilters.FILTER_CONTROLLED_CREATURE, + sourceAbility.getControllerId(), sourceAbility, game + ) + .stream() + .filter(Objects::nonNull) + .map(MageObject::getPower) + .mapToInt(MageInt::getValue) + .distinct() + .map(x -> 1) + .sum(); + } + + @Override + public SelvalaEagerTrailblazerValue copy() { + return this; + } + + @Override + public String getMessage() { + return "different power among creatures you control"; + } + + @Override + public String toString() { + return "1"; + } +} diff --git a/Mage.Sets/src/mage/cards/s/SentryBot.java b/Mage.Sets/src/mage/cards/s/SentryBot.java new file mode 100644 index 00000000000..2eb00c49f49 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SentryBot.java @@ -0,0 +1,72 @@ +package mage.cards.s; + +import java.util.UUID; + +import mage.MageInt; +import mage.abilities.common.BeginningOfCombatTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.common.PayEnergyCost; +import mage.abilities.dynamicvalue.common.CreaturesAttackingYouCount; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.cost.SpellCostReductionSourceEffect; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.abilities.effects.common.counter.GetEnergyCountersControllerEffect; +import mage.constants.SubType; +import mage.abilities.keyword.FlashAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.StaticFilters; + +/** + * @author Cguy7777 + */ +public final class SentryBot extends CardImpl { + + public SentryBot(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{4}{W}"); + + this.subtype.add(SubType.ROBOT); + this.power = new MageInt(2); + this.toughness = new MageInt(5); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // This spell costs {1} less to cast for each creature attacking you. + this.addAbility(new SimpleStaticAbility( + Zone.ALL, + new SpellCostReductionSourceEffect(CreaturesAttackingYouCount.instance) + .setText("this spell costs {1} less to cast for each creature attacking you")) + .addHint(CreaturesAttackingYouCount.getHint())); + + // When Sentry Bot enters the battlefield, you get {E} for each creature attacking you. + this.addAbility(new EntersBattlefieldTriggeredAbility( + new GetEnergyCountersControllerEffect(CreaturesAttackingYouCount.instance) + .setText("you get {E} for each creature attacking you")) + .addHint(CreaturesAttackingYouCount.getHint())); + + // At the beginning of combat on your turn, you may pay {E}{E}{E}. If you do, put a +1/+1 counter on each creature you control. + this.addAbility(new BeginningOfCombatTriggeredAbility( + new DoIfCostPaid( + new AddCountersAllEffect( + CounterType.P1P1.createInstance(), + StaticFilters.FILTER_PERMANENT_CREATURE_CONTROLLED), + new PayEnergyCost(3)), + TargetController.YOU, + false)); + } + + private SentryBot(final SentryBot card) { + super(card); + } + + @Override + public SentryBot copy() { + return new SentryBot(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SeraphicSteed.java b/Mage.Sets/src/mage/cards/s/SeraphicSteed.java new file mode 100644 index 00000000000..fc7eb00dc1a --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SeraphicSteed.java @@ -0,0 +1,52 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.AttacksWhileSaddledTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.abilities.keyword.LifelinkAbility; +import mage.abilities.keyword.SaddleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.game.permanent.token.Angel33Token; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class SeraphicSteed extends CardImpl { + + public SeraphicSteed(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}{W}"); + + this.subtype.add(SubType.UNICORN); + this.subtype.add(SubType.MOUNT); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // First strike + this.addAbility(FirstStrikeAbility.getInstance()); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // Whenever Seraphic Steed attacks while saddled, create a 3/3 white Angel creature token with flying. + this.addAbility(new AttacksWhileSaddledTriggeredAbility(new CreateTokenEffect(new Angel33Token()))); + + // Saddle 4 + this.addAbility(new SaddleAbility(4)); + + } + + private SeraphicSteed(final SeraphicSteed card) { + super(card); + } + + @Override + public SeraphicSteed copy() { + return new SeraphicSteed(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/ServantOfTheStinger.java b/Mage.Sets/src/mage/cards/s/ServantOfTheStinger.java new file mode 100644 index 00000000000..ce1d397992b --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/ServantOfTheStinger.java @@ -0,0 +1,57 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.condition.common.CommittedCrimeCondition; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; +import mage.abilities.keyword.DeathtouchAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetCardInLibrary; +import mage.watchers.common.CommittedCrimeWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ServantOfTheStinger extends CardImpl { + + public ServantOfTheStinger(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARLOCK); + this.power = new MageInt(1); + this.toughness = new MageInt(3); + + // Deathtouch + this.addAbility(DeathtouchAbility.getInstance()); + + // Whenever Servant of the Stinger deals combat damage to a player, if you've committed a crime this turn, you may sacrifice Servant of the Stinger. If you do, search your library for a card, put it into your hand, then shuffle. + this.addAbility(new ConditionalInterveningIfTriggeredAbility( + new DealsCombatDamageToAPlayerTriggeredAbility( + new DoIfCostPaid( + new SearchLibraryPutInHandEffect(new TargetCardInLibrary(), false), + new SacrificeSourceCost() + ), false + ), CommittedCrimeCondition.instance, "Whenever {this} deals combat damage to a player, " + + "if you've committed a crime this turn, you may sacrifice {this}. If you do, " + + "search your library for a card, put it into your hand, then shuffle." + ).addHint(CommittedCrimeCondition.getHint()), new CommittedCrimeWatcher()); + } + + private ServantOfTheStinger(final ServantOfTheStinger card) { + super(card); + } + + @Override + public ServantOfTheStinger copy() { + return new ServantOfTheStinger(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/ShackleSlinger.java b/Mage.Sets/src/mage/cards/s/ShackleSlinger.java new file mode 100644 index 00000000000..ca4f28b27bb --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/ShackleSlinger.java @@ -0,0 +1,76 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.CastSecondSpellTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetOpponentsCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ShackleSlinger extends CardImpl { + + public ShackleSlinger(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Whenever you cast your second spell each turn, choose target creature an opponent controls. If it's tapped, put a stun counter on it. Otherwise, tap it. + Ability ability = new CastSecondSpellTriggeredAbility(new ShackleSlingerEffect()); + ability.addTarget(new TargetOpponentsCreaturePermanent()); + this.addAbility(ability); + } + + private ShackleSlinger(final ShackleSlinger card) { + super(card); + } + + @Override + public ShackleSlinger copy() { + return new ShackleSlinger(this); + } +} + +class ShackleSlingerEffect extends OneShotEffect { + + ShackleSlingerEffect() { + super(Outcome.Benefit); + staticText = "choose target creature an opponent controls. " + + "If it's tapped, put a stun counter on it. Otherwise, tap it"; + } + + private ShackleSlingerEffect(final ShackleSlingerEffect effect) { + super(effect); + } + + @Override + public ShackleSlingerEffect copy() { + return new ShackleSlingerEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent == null) { + return false; + } + if (!permanent.isTapped()) { + return permanent.tap(source, game); + } + return permanent.addCounters(CounterType.STUN.createInstance(), source, game); + } +} diff --git a/Mage.Sets/src/mage/cards/s/ShepherdOfTheClouds.java b/Mage.Sets/src/mage/cards/s/ShepherdOfTheClouds.java new file mode 100644 index 00000000000..550417edf5b --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/ShepherdOfTheClouds.java @@ -0,0 +1,75 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect; +import mage.abilities.hint.ConditionHint; +import mage.abilities.hint.Hint; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.common.FilterControlledPermanent; +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 ShepherdOfTheClouds 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)); + } + + private static final Condition condition + = new PermanentsOnTheBattlefieldCondition(new FilterControlledPermanent(SubType.MOUNT)); + private static final Hint hint = new ConditionHint(condition, "You control a Mount"); + + public ShepherdOfTheClouds(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{W}"); + + this.subtype.add(SubType.PEGASUS); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // When Shepherd of the Clouds enters the battlefield, return target permanent card with mana value 3 or less from your graveyard to your hand. Return that card to the battlefield instead if you control a Mount. + Ability ability = new EntersBattlefieldTriggeredAbility(new ConditionalOneShotEffect( + new ReturnFromGraveyardToBattlefieldTargetEffect(), new ReturnFromGraveyardToHandTargetEffect(), + condition, "return target permanent card with mana value 3 or less from your graveyard " + + "to your hand. Return that card to the battlefield instead if you control a Mount" + )); + ability.addTarget(new TargetCardInYourGraveyard(filter)); + this.addAbility(ability.addHint(hint)); + } + + private ShepherdOfTheClouds(final ShepherdOfTheClouds card) { + super(card); + } + + @Override + public ShepherdOfTheClouds copy() { + return new ShepherdOfTheClouds(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SheriffOfSafePassage.java b/Mage.Sets/src/mage/cards/s/SheriffOfSafePassage.java new file mode 100644 index 00000000000..526b39ad450 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SheriffOfSafePassage.java @@ -0,0 +1,61 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.IntPlusDynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.abilities.keyword.PlotAbility; +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 java.util.UUID; + +/** + * @author Susucr + */ +public final class SheriffOfSafePassage extends CardImpl { + + private static final DynamicValue value = new PermanentsOnBattlefieldCount(StaticFilters.FILTER_OTHER_CONTROLLED_CREATURES); + private static final DynamicValue xValue = new IntPlusDynamicValue(1, value); + private static final Hint hint = new ValueHint("Other creatures you control", value); + + public SheriffOfSafePassage(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.KNIGHT); + this.power = new MageInt(0); + this.toughness = new MageInt(0); + + // Sheriff of Safe Passage enters the battlefield with a +1/+1 counter on it plus an additional +1/+1 counter on it for each other creature you control. + this.addAbility( + new EntersBattlefieldAbility( + new AddCountersSourceEffect( + CounterType.P1P1.createInstance(), + xValue, false + ), + "with a +1/+1 counter on it plus an additional +1/+1 counter on it for each other creature you control" + ).addHint(hint) + ); + + // Plot {1}{W} + this.addAbility(new PlotAbility("{1}{W}")); + } + + private SheriffOfSafePassage(final SheriffOfSafePassage card) { + super(card); + } + + @Override + public SheriffOfSafePassage copy() { + return new SheriffOfSafePassage(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/ShiftingGrift.java b/Mage.Sets/src/mage/cards/s/ShiftingGrift.java new file mode 100644 index 00000000000..ac26c1d043b --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/ShiftingGrift.java @@ -0,0 +1,53 @@ +package mage.cards.s; + +import mage.abilities.Mode; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.continuous.ExchangeControlTargetEffect; +import mage.abilities.keyword.SpreeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.target.common.TargetArtifactPermanent; +import mage.target.common.TargetCreaturePermanent; +import mage.target.common.TargetEnchantmentPermanent; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class ShiftingGrift extends CardImpl { + + public ShiftingGrift(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{U}{U}"); + + + // Spree + this.addAbility(new SpreeAbility(this)); + + // + {2} -- Exchange control of two target creatures. + this.getSpellAbility().addEffect(new ExchangeControlTargetEffect(Duration.EndOfGame, "Exchange control of two target creatures")); + this.getSpellAbility().addTarget(new TargetCreaturePermanent(2)); + this.getSpellAbility().withFirstModeCost(new GenericManaCost(2)); + + // + {1} -- Exchange control of two target artifacts. + this.getSpellAbility().addMode(new Mode(new ExchangeControlTargetEffect(Duration.EndOfGame, "Exchange control of two target artifacts")) + .addTarget(new TargetArtifactPermanent(2)) + .withCost(new GenericManaCost(1))); + + // + {1} -- Exchange control of two target enchantments. + this.getSpellAbility().addMode(new Mode(new ExchangeControlTargetEffect(Duration.EndOfGame, "Exchange control of two target enchantments")) + .addTarget(new TargetEnchantmentPermanent(2)) + .withCost(new GenericManaCost(1))); + } + + private ShiftingGrift(final ShiftingGrift card) { + super(card); + } + + @Override + public ShiftingGrift copy() { + return new ShiftingGrift(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/ShipwreckSentry.java b/Mage.Sets/src/mage/cards/s/ShipwreckSentry.java index 7f53ccd66a7..96c5a3d44cc 100644 --- a/Mage.Sets/src/mage/cards/s/ShipwreckSentry.java +++ b/Mage.Sets/src/mage/cards/s/ShipwreckSentry.java @@ -77,7 +77,7 @@ class ShipwreckSentryWatcher extends Watcher { } EntersTheBattlefieldEvent eEvent = (EntersTheBattlefieldEvent) event; if (eEvent.getTarget() != null && eEvent.getTarget().isArtifact(game)) { - playerSet.add(eEvent.getTarget().getId()); + playerSet.add(eEvent.getTarget().getControllerId()); } } diff --git a/Mage.Sets/src/mage/cards/s/ShootTheSheriff.java b/Mage.Sets/src/mage/cards/s/ShootTheSheriff.java new file mode 100644 index 00000000000..b0e7f3df6d1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/ShootTheSheriff.java @@ -0,0 +1,43 @@ +package mage.cards.s; + +import mage.abilities.effects.common.DestroyTargetEffect; +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.Predicates; +import mage.filter.predicate.mageobject.OutlawPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ShootTheSheriff extends CardImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent("non-outlaw creature"); + + static { + filter.add(Predicates.not(OutlawPredicate.instance)); + } + + public ShootTheSheriff(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{B}"); + + // Destroy target non-outlaw creature. + this.getSpellAbility().addEffect(new DestroyTargetEffect()); + this.getSpellAbility().addTarget(new TargetPermanent(filter)); + } + + private ShootTheSheriff(final ShootTheSheriff card) { + super(card); + } + + @Override + public ShootTheSheriff copy() { + return new ShootTheSheriff(this); + } +} +// everyone else is fair game diff --git a/Mage.Sets/src/mage/cards/s/SicarianInfiltrator.java b/Mage.Sets/src/mage/cards/s/SicarianInfiltrator.java index cad6ed5c26f..ed13f926485 100644 --- a/Mage.Sets/src/mage/cards/s/SicarianInfiltrator.java +++ b/Mage.Sets/src/mage/cards/s/SicarianInfiltrator.java @@ -2,6 +2,7 @@ package mage.cards.s; import mage.MageInt; import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.keyword.FlashAbility; import mage.abilities.keyword.SquadAbility; @@ -29,7 +30,7 @@ public final class SicarianInfiltrator extends CardImpl { this.addAbility(FlashAbility.getInstance()); // Squad {2} - this.addAbility(new SquadAbility()); + this.addAbility(new SquadAbility(new GenericManaCost(2))); // Benediction of Omnissiah -- When Sicarian Infiltrator enters the battlefield, draw a card. this.addAbility(new EntersBattlefieldTriggeredAbility( diff --git a/Mage.Sets/src/mage/cards/s/SigardaFontOfBlessings.java b/Mage.Sets/src/mage/cards/s/SigardaFontOfBlessings.java index 3e79b06a8e7..8144e940147 100644 --- a/Mage.Sets/src/mage/cards/s/SigardaFontOfBlessings.java +++ b/Mage.Sets/src/mage/cards/s/SigardaFontOfBlessings.java @@ -4,7 +4,7 @@ import mage.MageInt; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; -import mage.abilities.effects.common.continuous.PlayTheTopCardEffect; +import mage.abilities.effects.common.continuous.PlayFromTopOfLibraryEffect; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.HexproofAbility; import mage.cards.CardImpl; @@ -51,9 +51,7 @@ public final class SigardaFontOfBlessings extends CardImpl { this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); // You may cast Angel spells and Human spells from the top of your library. - this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect( - TargetController.YOU, filter, false - ))); + this.addAbility(new SimpleStaticAbility(new PlayFromTopOfLibraryEffect(filter))); } private SigardaFontOfBlessings(final SigardaFontOfBlessings card) { diff --git a/Mage.Sets/src/mage/cards/s/SilverDeputy.java b/Mage.Sets/src/mage/cards/s/SilverDeputy.java new file mode 100644 index 00000000000..c09e812fc86 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SilverDeputy.java @@ -0,0 +1,63 @@ +package mage.cards.s; + +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.continuous.BoostTargetEffect; +import mage.abilities.effects.common.search.SearchLibraryPutOnLibraryEffect; +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.common.FilterLandCard; +import mage.filter.predicate.Predicates; +import mage.target.common.TargetCardInLibrary; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class SilverDeputy extends CardImpl { + + private static final FilterCard filter = new FilterLandCard("a basic land card or a Desert card"); + + static { + filter.add(Predicates.or(SuperType.BASIC.getPredicate(), SubType.DESERT.getPredicate())); + } + + public SilverDeputy(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{2}"); + + this.subtype.add(SubType.MERCENARY); + this.power = new MageInt(1); + this.toughness = new MageInt(2); + + // When Silver Deputy enters the battlefield, you may search your library for a basic land card or a Desert card, reveal it, then shuffle and put it on top. + this.addAbility(new EntersBattlefieldTriggeredAbility( + new SearchLibraryPutOnLibraryEffect(new TargetCardInLibrary(filter), true), + true + )); + + // {T}: Target creature you control gets +1/+0 until end of turn. Activate only as a sorcery. + Ability ability = new ActivateAsSorceryActivatedAbility( + new BoostTargetEffect(1, 0), new TapSourceCost() + ); + ability.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(ability); + } + + private SilverDeputy(final SilverDeputy card) { + super(card); + } + + @Override + public SilverDeputy copy() { + return new SilverDeputy(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SilverShroudCostume.java b/Mage.Sets/src/mage/cards/s/SilverShroudCostume.java index 31e6232a1a0..816733ea926 100644 --- a/Mage.Sets/src/mage/cards/s/SilverShroudCostume.java +++ b/Mage.Sets/src/mage/cards/s/SilverShroudCostume.java @@ -43,7 +43,7 @@ public final class SilverShroudCostume extends CardImpl { this.addAbility(new SimpleStaticAbility(new CantBeBlockedAttachedEffect(AttachmentType.EQUIPMENT))); // Equip {3} - this.addAbility(new EquipAbility(3)); + this.addAbility(new EquipAbility(3, false)); } private SilverShroudCostume(final SilverShroudCostume card) { diff --git a/Mage.Sets/src/mage/cards/s/SimulacrumSynthesizer.java b/Mage.Sets/src/mage/cards/s/SimulacrumSynthesizer.java new file mode 100644 index 00000000000..11fca4d2a84 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SimulacrumSynthesizer.java @@ -0,0 +1,52 @@ +package mage.cards.s; + +import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +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.ComparisonType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterArtifactPermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.filter.predicate.mageobject.ManaValuePredicate; +import mage.game.permanent.token.KarnConstructToken; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class SimulacrumSynthesizer extends CardImpl { + + private static final FilterPermanent filter = + new FilterArtifactPermanent("another artifact with mana value 3 or greater"); + + static { + filter.add(AnotherPredicate.instance); + filter.add(new ManaValuePredicate(ComparisonType.OR_GREATER, 3)); + } + + public SimulacrumSynthesizer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{U}"); + + // When Substitute Synthesizer enters the battlefield, scry 2. + this.addAbility(new EntersBattlefieldTriggeredAbility(new ScryEffect(2))); + + // Whenever another artifact with mana value 3 or more enters the battlefield under your control, create a 0/0 colorless Construct artifact creature token with "This creature gets +1/+1 for each artifact you control." + this.addAbility(new EntersBattlefieldControlledTriggeredAbility( + new CreateTokenEffect(new KarnConstructToken()), filter + )); + } + + private SimulacrumSynthesizer(final SimulacrumSynthesizer card) { + super(card); + } + + @Override + public SimulacrumSynthesizer copy() { + return new SimulacrumSynthesizer(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SinsOfThePast.java b/Mage.Sets/src/mage/cards/s/SinsOfThePast.java index 8d0aac8ccfe..66436ab12f2 100644 --- a/Mage.Sets/src/mage/cards/s/SinsOfThePast.java +++ b/Mage.Sets/src/mage/cards/s/SinsOfThePast.java @@ -1,11 +1,12 @@ package mage.cards.s; import mage.abilities.effects.common.ExileSpellEffect; -import mage.abilities.effects.common.MayCastTargetThenExileEffect; +import mage.abilities.effects.common.MayCastTargetCardEffect; import mage.abilities.effects.common.replacement.ThatSpellGraveyardExileReplacementEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.CastManaAdjustment; import mage.filter.StaticFilters; import mage.target.common.TargetCardInYourGraveyard; @@ -20,7 +21,7 @@ public final class SinsOfThePast extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{B}{B}"); // Until end of turn, you may cast target instant or sorcery card from your graveyard without paying its mana cost. If that card would be put into your graveyard this turn, exile it instead. Exile Sins of the Past. - this.getSpellAbility().addEffect(new MayCastTargetThenExileEffect(true) + this.getSpellAbility().addEffect(new MayCastTargetCardEffect(CastManaAdjustment.WITHOUT_PAYING_MANA_COST, true) .setText("Until end of turn, you may cast target instant or sorcery card from your graveyard without paying its mana cost. " + ThatSpellGraveyardExileReplacementEffect.RULE_YOUR)); this.getSpellAbility().addEffect(new ExileSpellEffect()); diff --git a/Mage.Sets/src/mage/cards/s/SlickSequence.java b/Mage.Sets/src/mage/cards/s/SlickSequence.java new file mode 100644 index 00000000000..4c5d96c7bd0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SlickSequence.java @@ -0,0 +1,116 @@ +package mage.cards.s; + +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.hint.ConditionHint; +import mage.abilities.hint.Hint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.WatcherScope; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.stack.Spell; +import mage.target.common.TargetAnyTarget; +import mage.watchers.Watcher; + +import java.util.*; + +/** + * @author Susucr + */ +public final class SlickSequence extends CardImpl { + + public SlickSequence(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{U}{R}"); + + // Slick Sequence deals 2 damage to any target. If you've cast another spell this turn, draw a card. + this.getSpellAbility().addEffect(new DamageTargetEffect(2)); + this.getSpellAbility().addTarget(new TargetAnyTarget()); + this.getSpellAbility().addEffect( + new ConditionalOneShotEffect( + new DrawCardSourceControllerEffect(1), + SlickSequenceCondition.instance + ) + ); + this.getSpellAbility().addHint(SlickSequenceCondition.hint); + this.getSpellAbility().addWatcher(new SlickSequenceWatcher()); + } + + private SlickSequence(final SlickSequence card) { + super(card); + } + + @Override + public SlickSequence copy() { + return new SlickSequence(this); + } +} + + +enum SlickSequenceCondition implements Condition { + instance; + static final Hint hint = new ConditionHint(instance, "you've cast another spell this turn"); + + @Override + public boolean apply(Game game, Ability source) { + SlickSequenceWatcher watcher = game.getState().getWatcher(SlickSequenceWatcher.class); + if (watcher == null) { + return false; + } + // may be null, watcher will handle that. + Spell sourceSpell = game.getSpell(source.getSourceId()); + return watcher.didPlayerCastAnotherSpellThisTurn(source.getControllerId(), sourceSpell, game); + } + + @Override + public String toString() { + return "you've cast another spell this turn"; + } +} + +class SlickSequenceWatcher extends Watcher { + + // Per player, MOR of the spells cast this turn. + private final Map> spellsCastThisTurn = new HashMap<>(); + + /** + * Game default watcher + */ + public SlickSequenceWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() == GameEvent.EventType.SPELL_CAST) { + UUID playerId = event.getPlayerId(); + if (playerId != null) { + MageObjectReference mor = new MageObjectReference(event.getTargetId(), game); + spellsCastThisTurn.computeIfAbsent(playerId, x -> new HashSet<>()).add(mor); + } + } + } + + @Override + public void reset() { + super.reset(); + spellsCastThisTurn.clear(); + } + + boolean didPlayerCastAnotherSpellThisTurn(UUID playerId, Spell spell, Game game) { + Set spells = spellsCastThisTurn.getOrDefault(playerId, new HashSet<>()); + if (spell == null) { + // Not currently a spell, so any spell recorded does count as another. + return !spells.isEmpty(); + } else { + // Is currently a spell, so need to exclude that one. + MageObjectReference spellMOR = new MageObjectReference(spell.getId(), game); + return spells.stream().anyMatch(mor -> !mor.equals(spellMOR)); + } + } +} diff --git a/Mage.Sets/src/mage/cards/s/SlickshotLockpicker.java b/Mage.Sets/src/mage/cards/s/SlickshotLockpicker.java new file mode 100644 index 00000000000..d5fdc1e10a5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SlickshotLockpicker.java @@ -0,0 +1,93 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.keyword.FlashbackAbility; +import mage.abilities.keyword.PlotAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicates; +import mage.game.Game; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class SlickshotLockpicker extends CardImpl { + + private static final FilterCard filter = new FilterCard("instant or sorcery card in your graveyard"); + + static { + filter.add(Predicates.or( + CardType.INSTANT.getPredicate(), + CardType.SORCERY.getPredicate() + )); + } + + public SlickshotLockpicker(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // When Slickshot Lockpicker enters the battlefield, target instant or sorcery card in your graveyard gains flashback until end of turn. The flashback cost is equal to its mana cost. + Ability ability = new EntersBattlefieldTriggeredAbility(new SlickshotLockpickerEffect()); + ability.addTarget(new TargetCardInYourGraveyard(filter)); + this.addAbility(ability); + + // Plot {2}{U} + this.addAbility(new PlotAbility("{2}{U}")); + } + + private SlickshotLockpicker(final SlickshotLockpicker card) { + super(card); + } + + @Override + public SlickshotLockpicker copy() { + return new SlickshotLockpicker(this); + } +} + +/** + * From {@link mage.cards.s.SnapcasterMage SnapcasterMage} + */ +class SlickshotLockpickerEffect extends ContinuousEffectImpl { + + SlickshotLockpickerEffect() { + super(Duration.EndOfTurn, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); + this.staticText = "target instant or sorcery card in your graveyard gains flashback until end of turn. " + + "The flashback cost is equal to its mana cost"; + } + + private SlickshotLockpickerEffect(final SlickshotLockpickerEffect effect) { + super(effect); + } + + @Override + public SlickshotLockpickerEffect copy() { + return new SlickshotLockpickerEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Card card = game.getCard(getTargetPointer().getFirst(game, source)); + if (card != null) { + FlashbackAbility ability = new FlashbackAbility(card, card.getManaCost()); + ability.setSourceId(card.getId()); + ability.setControllerId(card.getOwnerId()); + game.getState().addOtherAbility(card, ability); + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/s/SlickshotShowOff.java b/Mage.Sets/src/mage/cards/s/SlickshotShowOff.java new file mode 100644 index 00000000000..979c21fec93 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SlickshotShowOff.java @@ -0,0 +1,55 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.PlotAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SlickshotShowOff extends CardImpl { + + public SlickshotShowOff(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}"); + + this.subtype.add(SubType.BIRD); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(1); + this.toughness = new MageInt(2); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // Whenever you cast a noncreature spell, Slickshot Show-Off gets +2/+0 until end of turn. + this.addAbility(new SpellCastControllerTriggeredAbility( + new BoostSourceEffect(2, 0, Duration.EndOfTurn), + StaticFilters.FILTER_SPELL_A_NON_CREATURE, false + )); + + // Plot {1}{R} + this.addAbility(new PlotAbility("{1}{R}")); + } + + private SlickshotShowOff(final SlickshotShowOff card) { + super(card); + } + + @Override + public SlickshotShowOff copy() { + return new SlickshotShowOff(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SlickshotVaultBuster.java b/Mage.Sets/src/mage/cards/s/SlickshotVaultBuster.java new file mode 100644 index 00000000000..b26a568f854 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SlickshotVaultBuster.java @@ -0,0 +1,49 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.CommittedCrimeCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +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.watchers.common.CommittedCrimeWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SlickshotVaultBuster extends CardImpl { + + public SlickshotVaultBuster(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(1); + this.toughness = new MageInt(4); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Slickshot Vault-Buster gets +2/+0 as long as you've committed a crime this turn. + this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( + new BoostSourceEffect(2, 0, Duration.WhileOnBattlefield), + CommittedCrimeCondition.instance, "{this} gets +2/+0 as long as you've committed a crime this turn" + )).addHint(CommittedCrimeCondition.getHint()), new CommittedCrimeWatcher()); + } + + private SlickshotVaultBuster(final SlickshotVaultBuster card) { + super(card); + } + + @Override + public SlickshotVaultBuster copy() { + return new SlickshotVaultBuster(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SludgeTitan.java b/Mage.Sets/src/mage/cards/s/SludgeTitan.java index 6ad9e7d196e..18e40158d75 100644 --- a/Mage.Sets/src/mage/cards/s/SludgeTitan.java +++ b/Mage.Sets/src/mage/cards/s/SludgeTitan.java @@ -16,7 +16,7 @@ import mage.constants.Zone; import mage.game.Game; import mage.players.Player; import mage.target.TargetCard; -import mage.target.common.TargetCardAndOrCardInGraveyard; +import mage.target.common.TargetCardAndOrCard; import java.util.UUID; @@ -75,8 +75,9 @@ class SludgeTitanEffect extends OneShotEffect { } Cards cards = controller.millCards(5, source, game); game.getState().processAction(game); + cards.removeIf(card -> !game.getState().getZone(card).isPublicZone()); if (!cards.isEmpty()) { - TargetCard target = new TargetCardAndOrCardInGraveyard(CardType.CREATURE, CardType.LAND); + TargetCard target = new TargetCardAndOrCard(CardType.CREATURE, CardType.LAND); controller.choose(Outcome.DrawCard, cards, target, source, game); Cards toHand = new CardsImpl(); toHand.addAll(target.getTargets()); diff --git a/Mage.Sets/src/mage/cards/s/SmirkingSpelljacker.java b/Mage.Sets/src/mage/cards/s/SmirkingSpelljacker.java new file mode 100644 index 00000000000..137dde27d99 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SmirkingSpelljacker.java @@ -0,0 +1,129 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.effects.common.MayCastTargetCardEffect; +import mage.abilities.keyword.FlashAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.FilterSpell; +import mage.game.ExileZone; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.TargetSpell; +import mage.target.common.TargetCardInExile; +import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class SmirkingSpelljacker extends CardImpl { + + private static final FilterSpell filter = new FilterSpell("spell an opponent controls"); + + static { + filter.add(TargetController.OPPONENT.getControllerPredicate()); + } + + public SmirkingSpelljacker(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{U}"); + + this.subtype.add(SubType.DJINN); + this.subtype.add(SubType.WIZARD); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When Smirking Spelljacker enters the battlefield, exile target spell an opponent controls. + Ability ability = new EntersBattlefieldTriggeredAbility(new ExileTargetEffect().setToSourceExileZone(true)); + ability.addTarget(new TargetSpell(filter)); + this.addAbility(ability); + + // Whenever Smirking Spelljacker attacks, if a card is exiled with it, you may cast the exiled card without paying its mana cost. + this.addAbility(new ConditionalInterveningIfTriggeredAbility( + new AttacksTriggeredAbility(new SmirkingSpelljackerEffect()), + SmirkingSpelljackerCondition.instance, + "Whenever Smirking Spelljacker attacks, if a card is exiled with it, " + + "you may cast the exiled card without paying its mana cost." + )); + } + + private SmirkingSpelljacker(final SmirkingSpelljacker card) { + super(card); + } + + @Override + public SmirkingSpelljacker copy() { + return new SmirkingSpelljacker(this); + } +} + +enum SmirkingSpelljackerCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + MageObject sourceObject = source.getSourceObject(game); + ExileZone exile = game.getExile().getExileZone(CardUtil.getExileZoneId(game, sourceObject.getId(), sourceObject.getZoneChangeCounter(game))); + return exile != null && !exile.isEmpty(); + } + + @Override + public String toString() { + return "if a card is exiled with it"; + } +} + +class SmirkingSpelljackerEffect extends OneShotEffect { + + SmirkingSpelljackerEffect() { + super(Outcome.PlayForFree); + staticText = "you may cast the exiled card without paying its mana cost"; + } + + private SmirkingSpelljackerEffect(final SmirkingSpelljackerEffect effect) { + super(effect); + } + + @Override + public SmirkingSpelljackerEffect copy() { + return new SmirkingSpelljackerEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + FilterCard filter = new FilterCard("card exiled with " + CardUtil.getSourceLogName(game, source)); + TargetCard target = new TargetCardInExile(1, 1, filter, CardUtil.getExileZoneId(game, source)); + target.withNotTarget(true); + controller.choose(Outcome.PlayForFree, target, source, game); + new MayCastTargetCardEffect(CastManaAdjustment.WITHOUT_PAYING_MANA_COST) + .setTargetPointer(new FixedTarget(target.getFirstTarget())) + .apply(game, source); + return true; + } + +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/s/SmugglersSurprise.java b/Mage.Sets/src/mage/cards/s/SmugglersSurprise.java new file mode 100644 index 00000000000..c62f9a50522 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SmugglersSurprise.java @@ -0,0 +1,113 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.MillThenPutInHandEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.HexproofAbility; +import mage.abilities.keyword.IndestructibleAbility; +import mage.abilities.keyword.SpreeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.CardsImpl; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreatureCard; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.PowerPredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCardInHand; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class SmugglersSurprise extends CardImpl { + + private static final FilterCard filterCard = new FilterCard("creature and/or land cards"); + + static { + filterCard.add(Predicates.or(CardType.CREATURE.getPredicate(), CardType.LAND.getPredicate())); + } + + private static final FilterPermanent filter = new FilterCreaturePermanent("Creatures you control with power 4 or greater"); + + static { + filter.add(new PowerPredicate(ComparisonType.OR_GREATER, 4)); + } + + public SmugglersSurprise(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{G}"); + + // Spree + this.addAbility(new SpreeAbility(this)); + + // + {2} -- Mill four cards. You may put up to two creature and/or land cards from among the milled cards into your hand. + this.getSpellAbility().addEffect(new MillThenPutInHandEffect( + 4, filterCard, null, true, 2 + )); + this.getSpellAbility().withFirstModeCost(new GenericManaCost(2)); + + // + {4}{G} -- You may put up to two creature cards from your hand onto the battlefield. + this.getSpellAbility().addMode(new Mode(new SmugglersSurpriseEffect()) + .withCost(new ManaCostsImpl<>("{4}{G}"))); + + // + {1} -- Creatures you control with power 4 or greater gain hexproof and indestructible until end of turn. + this.getSpellAbility().addMode(new Mode( + new GainAbilityControlledEffect(HexproofAbility.getInstance(), Duration.EndOfTurn, filter) + .setText("Creatures you control with power 4 or greater gain hexproof")) + .addEffect(new GainAbilityControlledEffect(IndestructibleAbility.getInstance(), Duration.EndOfTurn, filter) + .setText(" and indestructible until end of turn")) + .withCost(new GenericManaCost(1)) + ); + } + + private SmugglersSurprise(final SmugglersSurprise card) { + super(card); + } + + @Override + public SmugglersSurprise copy() { + return new SmugglersSurprise(this); + } +} + +class SmugglersSurpriseEffect extends OneShotEffect { + + SmugglersSurpriseEffect() { + super(Outcome.PutCreatureInPlay); + this.staticText = "You may put up to two creature cards from your hand onto the battlefield"; + } + + private SmugglersSurpriseEffect(final SmugglersSurpriseEffect effect) { + super(effect); + } + + @Override + public SmugglersSurpriseEffect copy() { + return new SmugglersSurpriseEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + TargetCardInHand target = new TargetCardInHand(0, 2, new FilterCreatureCard("creature cards")); + if (controller.choose(Outcome.PutCreatureInPlay, target, source, game)) { + return controller.moveCards( + new CardsImpl(target.getTargets()).getCards(game), + Zone.BATTLEFIELD, source, game + ); + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/s/SontaranGeneral.java b/Mage.Sets/src/mage/cards/s/SontaranGeneral.java new file mode 100644 index 00000000000..7bf0867ba46 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SontaranGeneral.java @@ -0,0 +1,57 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.effects.common.combat.CantBlockTargetEffect; +import mage.abilities.effects.common.combat.GoadTargetEffect; +import mage.abilities.keyword.BattalionAbility; +import mage.abilities.keyword.HasteAbility; +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.target.common.TargetCreaturePermanent; +import mage.target.targetadjustment.EachOpponentPermanentTargetsAdjuster; + +import java.util.UUID; + +/** + * + * @author notgreat + */ +public final class SontaranGeneral extends CardImpl { + + public SontaranGeneral(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{R}"); + this.subtype.add(SubType.ALIEN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // Battalion -- Whenever Sontaran General and at least two other creatures attack, for each opponent, + // goad up to one target creature that player controls. Those creatures can't block this turn. + Ability ability = new BattalionAbility(new GoadTargetEffect() + .setText("for each opponent, goad up to one target creature that player controls.")); + ability.addEffect(new CantBlockTargetEffect(Duration.EndOfTurn) + .setText("Those creatures can't block this turn.")); + ability.setTargetAdjuster(new EachOpponentPermanentTargetsAdjuster(new TargetCreaturePermanent(0, 1))); + this.addAbility(ability); + } + + private SontaranGeneral(final SontaranGeneral card) { + super(card); + } + + @Override + public SontaranGeneral copy() { + return new SontaranGeneral(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SorcerousSquall.java b/Mage.Sets/src/mage/cards/s/SorcerousSquall.java index 9fc9aeb1a79..398c10c4d73 100644 --- a/Mage.Sets/src/mage/cards/s/SorcerousSquall.java +++ b/Mage.Sets/src/mage/cards/s/SorcerousSquall.java @@ -3,13 +3,14 @@ package mage.cards.s; import mage.abilities.Ability; import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.MayCastTargetThenExileEffect; +import mage.abilities.effects.common.MayCastTargetCardEffect; import mage.abilities.effects.common.MillCardsTargetEffect; import mage.abilities.effects.common.replacement.ThatSpellGraveyardExileReplacementEffect; import mage.abilities.keyword.DelveAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.CastManaAdjustment; import mage.constants.Outcome; import mage.filter.FilterCard; import mage.filter.common.FilterInstantOrSorceryCard; @@ -24,7 +25,6 @@ import mage.target.targetpointer.FixedTarget; import java.util.UUID; /** - * * @author notgreat */ public final class SorcerousSquall extends CardImpl { @@ -57,7 +57,7 @@ class SorcerousSquallEffect extends OneShotEffect { SorcerousSquallEffect() { super(Outcome.Detriment); - setText("you may cast an instant or sorcery spell from that player's graveyard without paying its mana cost. "+ ThatSpellGraveyardExileReplacementEffect.RULE_A); + setText("you may cast an instant or sorcery spell from that player's graveyard without paying its mana cost. " + ThatSpellGraveyardExileReplacementEffect.RULE_A); } private SorcerousSquallEffect(final SorcerousSquallEffect effect) { @@ -79,7 +79,7 @@ class SorcerousSquallEffect extends OneShotEffect { filter.add(new OwnerIdPredicate(source.getFirstTarget())); Target target = new TargetCardInGraveyard(1, 1, filter, true); player.choose(outcome, target, source, game); - Effect effect = new MayCastTargetThenExileEffect(true); + Effect effect = new MayCastTargetCardEffect(CastManaAdjustment.WITHOUT_PAYING_MANA_COST, true); effect.setTargetPointer(new FixedTarget(target.getFirstTarget(), game)); effect.apply(game, source); return true; diff --git a/Mage.Sets/src/mage/cards/s/SoulsOfTheFaultless.java b/Mage.Sets/src/mage/cards/s/SoulsOfTheFaultless.java index cbb3cc36d90..dd1528d9297 100644 --- a/Mage.Sets/src/mage/cards/s/SoulsOfTheFaultless.java +++ b/Mage.Sets/src/mage/cards/s/SoulsOfTheFaultless.java @@ -15,9 +15,8 @@ import mage.constants.SubType; import mage.constants.Outcome; import mage.constants.Zone; import mage.game.Game; -import mage.game.events.DamagedEvent; +import mage.game.events.DamagedBatchForOnePermanentEvent; import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; import mage.players.Player; /** @@ -67,20 +66,19 @@ class SoulsOfTheFaultlessTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT; } @Override public boolean checkTrigger(GameEvent event, Game game) { - if (event.getTargetId().equals(this.sourceId) - && ((DamagedEvent) event).isCombatDamage()) { - Permanent source = game.getPermanent(event.getSourceId()); - if (source == null) { - source = (Permanent) game.getLastKnownInformation(event.getSourceId(), Zone.BATTLEFIELD); - } - UUID attackerId = source != null ? source.getControllerId() : null; + DamagedBatchForOnePermanentEvent dEvent = (DamagedBatchForOnePermanentEvent) event; + + int damage = dEvent.getAmount(); + + if (dEvent.getTargetId().equals(this.sourceId) && dEvent.isCombatDamage() && damage > 0) { + UUID attackerId = game.getActivePlayerId(); for (Effect effect : this.getEffects()) { - effect.setValue("damageAmount", event.getAmount()); + effect.setValue("damageAmount", damage); effect.setValue("attackerId", attackerId); } return true; diff --git a/Mage.Sets/src/mage/cards/s/SouredSprings.java b/Mage.Sets/src/mage/cards/s/SouredSprings.java new file mode 100644 index 00000000000..6181da03a59 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SouredSprings.java @@ -0,0 +1,48 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTappedAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.mana.BlackManaAbility; +import mage.abilities.mana.BlueManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SouredSprings extends CardImpl { + + public SouredSprings(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + this.subtype.add(SubType.DESERT); + + // Soured Springs enters the battlefield tapped. + this.addAbility(new EntersBattlefieldTappedAbility()); + + // When Soured Springs enters the battlefield, it deals 1 damage to target opponent. + Ability ability = new EntersBattlefieldTriggeredAbility(new DamageTargetEffect(1, "it")); + ability.addTarget(new TargetOpponent()); + this.addAbility(ability); + + // {T}: Add {U} or {B}. + this.addAbility(new BlueManaAbility()); + this.addAbility(new BlackManaAbility()); + } + + private SouredSprings(final SouredSprings card) { + super(card); + } + + @Override + public SouredSprings copy() { + return new SouredSprings(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SowerOfDiscord.java b/Mage.Sets/src/mage/cards/s/SowerOfDiscord.java index 2fed274fae1..308c0337f12 100644 --- a/Mage.Sets/src/mage/cards/s/SowerOfDiscord.java +++ b/Mage.Sets/src/mage/cards/s/SowerOfDiscord.java @@ -124,7 +124,7 @@ class SowerOfDiscordTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PLAYER; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PLAYER; } @Override diff --git a/Mage.Sets/src/mage/cards/s/SpaceMarineDevastator.java b/Mage.Sets/src/mage/cards/s/SpaceMarineDevastator.java index a405d023e9d..b47e4084ef2 100644 --- a/Mage.Sets/src/mage/cards/s/SpaceMarineDevastator.java +++ b/Mage.Sets/src/mage/cards/s/SpaceMarineDevastator.java @@ -3,6 +3,7 @@ package mage.cards.s; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.common.DestroyTargetEffect; import mage.abilities.keyword.SquadAbility; import mage.cards.CardImpl; @@ -28,7 +29,7 @@ public final class SpaceMarineDevastator extends CardImpl { this.toughness = new MageInt(3); // Squad {2} - this.addAbility(new SquadAbility()); + this.addAbility(new SquadAbility(new GenericManaCost(2))); // Grav-cannon -- When Space Marine Devastator enters the battlefield, destroy up to one target artifact or enchantment. Ability ability = new EntersBattlefieldTriggeredAbility(new DestroyTargetEffect()); diff --git a/Mage.Sets/src/mage/cards/s/SpinewoodsArmadillo.java b/Mage.Sets/src/mage/cards/s/SpinewoodsArmadillo.java new file mode 100644 index 00000000000..88e0a1ac08f --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SpinewoodsArmadillo.java @@ -0,0 +1,72 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.DiscardSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; +import mage.abilities.keyword.ReachAbility; +import mage.abilities.keyword.WardAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicates; +import mage.target.common.TargetCardInLibrary; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SpinewoodsArmadillo extends CardImpl { + + private static final FilterCard filter = new FilterCard("basic land card or a Desert card"); + + static { + filter.add(Predicates.or( + Predicates.and( + SuperType.BASIC.getPredicate(), + CardType.LAND.getPredicate() + ), SubType.DESERT.getPredicate() + )); + } + + public SpinewoodsArmadillo(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G}{G}"); + + this.subtype.add(SubType.ARMADILLO); + this.power = new MageInt(7); + this.toughness = new MageInt(7); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // Ward {3} + this.addAbility(new WardAbility(new ManaCostsImpl<>("{3}"))); + + // {1}{G}, Discard Spinewoods Armadillo: Search your library for a basic land card or a Desert card, reveal it, put it into your hand, then shuffle. You gain 3 life. + Ability ability = new SimpleActivatedAbility( + Zone.HAND, + new SearchLibraryPutInHandEffect(new TargetCardInLibrary(filter), true), + new ManaCostsImpl<>("{1}{G}") + ); + ability.addCost(new DiscardSourceCost()); + ability.addEffect(new GainLifeEffect(3)); + this.addAbility(ability); + } + + private SpinewoodsArmadillo(final SpinewoodsArmadillo card) { + super(card); + } + + @Override + public SpinewoodsArmadillo copy() { + return new SpinewoodsArmadillo(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SpinewoodsPaladin.java b/Mage.Sets/src/mage/cards/s/SpinewoodsPaladin.java new file mode 100644 index 00000000000..5fd4d4cb3e9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SpinewoodsPaladin.java @@ -0,0 +1,46 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.keyword.PlotAbility; +import mage.abilities.keyword.TrampleAbility; +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 SpinewoodsPaladin extends CardImpl { + + public SpinewoodsPaladin(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.KNIGHT); + this.power = new MageInt(5); + this.toughness = new MageInt(4); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // When Spinewoods Paladin enters the battlefield, you gain 3 life. + this.addAbility(new EntersBattlefieldTriggeredAbility(new GainLifeEffect(3))); + + // Plot {3}{G} + this.addAbility(new PlotAbility("{3}{G}")); + } + + private SpinewoodsPaladin(final SpinewoodsPaladin card) { + super(card); + } + + @Override + public SpinewoodsPaladin copy() { + return new SpinewoodsPaladin(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SpringSplasher.java b/Mage.Sets/src/mage/cards/s/SpringSplasher.java new file mode 100644 index 00000000000..d40dabdedd1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SpringSplasher.java @@ -0,0 +1,51 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +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.permanent.DefendingPlayerControlsSourceAttackingPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SpringSplasher extends CardImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent("creature defending player controls"); + + static { + filter.add(DefendingPlayerControlsSourceAttackingPredicate.instance); + } + + public SpringSplasher(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}"); + + this.subtype.add(SubType.FROG); + this.subtype.add(SubType.BEAST); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // Whenever Spring Splasher attacks, target creature defending player controls gets -3/-0 until end of turn. + Ability ability = new AttacksTriggeredAbility(new BoostTargetEffect(-3, 0)); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + } + + private SpringSplasher(final SpringSplasher card) { + super(card); + } + + @Override + public SpringSplasher copy() { + return new SpringSplasher(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/StagecoachSecurity.java b/Mage.Sets/src/mage/cards/s/StagecoachSecurity.java new file mode 100644 index 00000000000..8e5fef8ac6b --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/StagecoachSecurity.java @@ -0,0 +1,54 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.PlotAbility; +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.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class StagecoachSecurity extends CardImpl { + + public StagecoachSecurity(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(4); + this.toughness = new MageInt(5); + + // When Stagecoach Security enters the battlefield, creatures you control get +1/+1 and gain vigilance until end of turn. + Ability ability = new EntersBattlefieldTriggeredAbility(new BoostControlledEffect( + 1, 1, Duration.EndOfTurn + ).setText("creatures you control get +1/+1")); + ability.addEffect(new GainAbilityControlledEffect( + VigilanceAbility.getInstance(), Duration.EndOfTurn, + StaticFilters.FILTER_CONTROLLED_CREATURE + ).setText("and gain vigilance until end of turn")); + this.addAbility(ability); + + // Plot {3}{W} + this.addAbility(new PlotAbility("{3}{W}")); + } + + private StagecoachSecurity(final StagecoachSecurity card) { + super(card); + } + + @Override + public StagecoachSecurity copy() { + return new StagecoachSecurity(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SteelSquirrel.java b/Mage.Sets/src/mage/cards/s/SteelSquirrel.java index ce88b1351ac..20fd20a11a8 100644 --- a/Mage.Sets/src/mage/cards/s/SteelSquirrel.java +++ b/Mage.Sets/src/mage/cards/s/SteelSquirrel.java @@ -75,7 +75,7 @@ class SteelSquirrelTriggeredAbility extends TriggeredAbilityImpl { public boolean checkTrigger(GameEvent event, Game game) { DieRolledEvent drEvent = (DieRolledEvent) event; // silver border card must look for "result" instead "natural result" - if (this.isControlledBy(event.getPlayerId()) && drEvent.getResult() < 5) { + if (this.isControlledBy(event.getTargetId()) && drEvent.getResult() < 5) { return false; } diff --git a/Mage.Sets/src/mage/cards/s/SteerClear.java b/Mage.Sets/src/mage/cards/s/SteerClear.java new file mode 100644 index 00000000000..9103479af2e --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SteerClear.java @@ -0,0 +1,133 @@ +package mage.cards.s; + +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.hint.ConditionTrueHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.WatcherScope; +import mage.filter.FilterPermanent; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.stack.Spell; +import mage.target.common.TargetAttackingOrBlockingCreature; +import mage.watchers.Watcher; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** + * @author Susucr + */ +public final class SteerClear extends CardImpl { + + public SteerClear(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{W}"); + + // Steer Clear deals 2 damage to target attacking or blocking creature. Steer Clear deals 4 damage to that creature instead if you controlled a Mount as you cast this spell. + this.getSpellAbility().addEffect(new SteerClearEffect()); + this.getSpellAbility().addWatcher(new SteerClearWatcher()); + this.getSpellAbility().addTarget(new TargetAttackingOrBlockingCreature()); + this.getSpellAbility().addHint(new ConditionTrueHint(SteerClearCondition.instance)); + } + + private SteerClear(final SteerClear card) { + super(card); + } + + @Override + public SteerClear copy() { + return new SteerClear(this); + } +} + +enum SteerClearCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + SteerClearWatcher watcher = game.getState().getWatcher(SteerClearWatcher.class); + return watcher != null + && watcher.didControlMountOnCast(new MageObjectReference(source.getSourceObject(game), game)); + } + + @Override + public String toString() { + return "if you controlled a Mount as you cast this spell"; + } +} + +class SteerClearEffect extends OneShotEffect { + + SteerClearEffect() { + super(Outcome.Damage); + this.staticText = "{this} deals 2 damage to target attacking or blocking creature. " + + "{this} deals 4 damage to that creature instead if you controlled a Mount as you cast this spell."; + } + + private SteerClearEffect(final SteerClearEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + SteerClearWatcher watcher = game.getState().getWatcher(SteerClearWatcher.class); + if (watcher == null) { + return false; + } + int amount = watcher.didControlMountOnCast(new MageObjectReference(source.getSourceObject(game), game)) + ? 4 : 2; + return new DamageTargetEffect(amount) + .setTargetPointer(getTargetPointer().copy()) + .apply(game, source); + } + + @Override + public SteerClearEffect copy() { + return new SteerClearEffect(this); + } + +} + +class SteerClearWatcher extends Watcher { + + private final Set spellsCastWithMountControlled = new HashSet<>(); + + private final FilterPermanent filter = new FilterPermanent(SubType.MOUNT, ""); + + SteerClearWatcher() { + 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) { + return; + } + if (0 == game.getBattlefield().countAll(filter, spell.getControllerId(), game)) { + return; + } + spellsCastWithMountControlled.add(new MageObjectReference(spell, game)); + } + + @Override + public void reset() { + super.reset(); + spellsCastWithMountControlled.clear(); + } + + public boolean didControlMountOnCast(MageObjectReference mor) { + return spellsCastWithMountControlled.contains(mor); + } +} diff --git a/Mage.Sets/src/mage/cards/s/StellaLeeWildCard.java b/Mage.Sets/src/mage/cards/s/StellaLeeWildCard.java new file mode 100644 index 00000000000..9060a6e6a69 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/StellaLeeWildCard.java @@ -0,0 +1,82 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.ActivateIfConditionActivatedAbility; +import mage.abilities.common.CastSecondSpellTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.effects.common.CopyTargetSpellEffect; +import mage.abilities.effects.common.ExileTopXMayPlayUntilEffect; +import mage.abilities.keyword.StormAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterSpell; +import mage.filter.common.FilterInstantOrSorcerySpell; +import mage.game.Game; +import mage.target.TargetSpell; +import mage.watchers.common.CastSpellLastTurnWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class StellaLeeWildCard extends CardImpl { + + private static final FilterSpell filter = new FilterInstantOrSorcerySpell("instant or sorcery spell you control"); + + static { + filter.add(TargetController.YOU.getControllerPredicate()); + } + + public StellaLeeWildCard(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.ROGUE); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // Whenever you cast your second spell each turn, exile the top card of your library. Until the end of your next turn, you may play that card. + this.addAbility(new CastSecondSpellTriggeredAbility( + new ExileTopXMayPlayUntilEffect(1, Duration.UntilEndOfYourNextTurn) + )); + + // {T}: Copy target instant or sorcery spell you control. You may choose new targets for the copy. Activate only if you've cast three or more spells this turn. + Ability ability = new ActivateIfConditionActivatedAbility( + Zone.BATTLEFIELD, new CopyTargetSpellEffect(), + new TapSourceCost(), StellaLeeWildCardCondition.instance + ); + ability.addTarget(new TargetSpell(filter)); + this.addAbility(ability.addHint(StormAbility.getHint())); + } + + private StellaLeeWildCard(final StellaLeeWildCard card) { + super(card); + } + + @Override + public StellaLeeWildCard copy() { + return new StellaLeeWildCard(this); + } +} + +enum StellaLeeWildCardCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + return game + .getState() + .getWatcher(CastSpellLastTurnWatcher.class) + .getAmountOfSpellsPlayerCastOnCurrentTurn(source.getControllerId()) >= 3; + } + + @Override + public String toString() { + return "you've cast three or more spells this turn"; + } +} diff --git a/Mage.Sets/src/mage/cards/s/StepBetweenWorlds.java b/Mage.Sets/src/mage/cards/s/StepBetweenWorlds.java new file mode 100644 index 00000000000..dc04bdb368a --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/StepBetweenWorlds.java @@ -0,0 +1,92 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileSpellEffect; +import mage.abilities.keyword.PlotAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.game.Game; +import mage.players.Player; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +/** + * @author Susucr + */ +public final class StepBetweenWorlds extends CardImpl { + + public StepBetweenWorlds(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{U}{U}"); + + // Each player may shuffle their hand and graveyard into their library. Each player who does draws seven cards. Exile Step Between Worlds. + this.getSpellAbility().addEffect(new StepBetweenWorldsEffect()); + this.getSpellAbility().addEffect(new ExileSpellEffect()); + + // Plot {4}{U}{U} + this.addAbility(new PlotAbility("{4}{U}{U}")); + } + + private StepBetweenWorlds(final StepBetweenWorlds card) { + super(card); + } + + @Override + public StepBetweenWorlds copy() { + return new StepBetweenWorlds(this); + } +} + +class StepBetweenWorldsEffect extends OneShotEffect { + + StepBetweenWorldsEffect() { + super(Outcome.DrawCard); + this.staticText = "Each player may shuffle their hand and graveyard into their library. Each player who does draws seven cards"; + } + + private StepBetweenWorldsEffect(final StepBetweenWorldsEffect effect) { + super(effect); + } + + @Override + public StepBetweenWorldsEffect copy() { + return new StepBetweenWorldsEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + List players = new ArrayList<>(); + for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { + Player player = game.getPlayer(playerId); + if (player == null) { + continue; + } + if (!player.chooseUse( + Outcome.Benefit, + "Shuffle your hand and graveyard into your library, then draw seven cards?", + source, game + )) { + continue; + } + players.add(player); + Cards cards = new CardsImpl(player.getHand()); + cards.addAll(player.getGraveyard()); + player.putCardsOnTopOfLibrary(cards, game, source, false); + player.shuffleLibrary(source, game); + } + for (Player player : players) { + player.drawCards(7, source, game); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/s/SterlingHound.java b/Mage.Sets/src/mage/cards/s/SterlingHound.java new file mode 100644 index 00000000000..29d0852a184 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SterlingHound.java @@ -0,0 +1,37 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.keyword.SurveilEffect; +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 SterlingHound extends CardImpl { + + public SterlingHound(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{3}"); + + this.subtype.add(SubType.DOG); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // When Sterling Hound enters the battlefield, surveil 2. + this.addAbility(new EntersBattlefieldTriggeredAbility(new SurveilEffect(2))); + } + + private SterlingHound(final SterlingHound card) { + super(card); + } + + @Override + public SterlingHound copy() { + return new SterlingHound(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SterlingKeykeeper.java b/Mage.Sets/src/mage/cards/s/SterlingKeykeeper.java new file mode 100644 index 00000000000..c0d8270a824 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SterlingKeykeeper.java @@ -0,0 +1,54 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.TapTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.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 SterlingKeykeeper extends CardImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent("non-Mount creature"); + + static { + filter.add(Predicates.not(SubType.MOUNT.getPredicate())); + } + + public SterlingKeykeeper(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.MERCENARY); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // {2}, {T}: Tap target non-Mount creature. + Ability ability = new SimpleActivatedAbility(new TapTargetEffect(), new GenericManaCost(2)); + ability.addCost(new TapSourceCost()); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + } + + private SterlingKeykeeper(final SterlingKeykeeper card) { + super(card); + } + + @Override + public SterlingKeykeeper copy() { + return new SterlingKeykeeper(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SterlingSupplier.java b/Mage.Sets/src/mage/cards/s/SterlingSupplier.java new file mode 100644 index 00000000000..ffb5f3f7541 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SterlingSupplier.java @@ -0,0 +1,48 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.keyword.FlyingAbility; +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 SterlingSupplier extends CardImpl { + + public SterlingSupplier(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{W}"); + + this.subtype.add(SubType.BIRD); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When Sterling Supplier enters the battlefield, put a +1/+1 counter on another target creature you control. + Ability ability = new EntersBattlefieldTriggeredAbility(new AddCountersTargetEffect(CounterType.P1P1.createInstance())); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_ANOTHER_TARGET_CREATURE_YOU_CONTROL)); + this.addAbility(ability); + } + + private SterlingSupplier(final SterlingSupplier card) { + super(card); + } + + @Override + public SterlingSupplier copy() { + return new SterlingSupplier(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/StingerbackTerror.java b/Mage.Sets/src/mage/cards/s/StingerbackTerror.java new file mode 100644 index 00000000000..dcef50e3f04 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/StingerbackTerror.java @@ -0,0 +1,58 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CardsInControllerHandCount; +import mage.abilities.dynamicvalue.common.SignInversionDynamicValue; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.PlotAbility; +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 java.util.UUID; + +/** + * @author TheElk801 + */ +public final class StingerbackTerror extends CardImpl { + + private static final DynamicValue xValue = new SignInversionDynamicValue(CardsInControllerHandCount.instance); + + public StingerbackTerror(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}{R}"); + + this.subtype.add(SubType.SCORPION); + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(7); + this.toughness = new MageInt(7); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Stingerback Terror gets -1/-1 for each card in your hand. + this.addAbility(new SimpleStaticAbility(new BoostSourceEffect( + xValue, xValue, Duration.WhileOnBattlefield + ))); + + // Plot {2}{R} + this.addAbility(new PlotAbility("{2}{R}")); + } + + private StingerbackTerror(final StingerbackTerror card) { + super(card); + } + + @Override + public StingerbackTerror copy() { + return new StingerbackTerror(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/StoicSphinx.java b/Mage.Sets/src/mage/cards/s/StoicSphinx.java new file mode 100644 index 00000000000..9e1d68b2986 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/StoicSphinx.java @@ -0,0 +1,53 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.HaventCastSpellThisTurnCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.keyword.FlashAbility; +import mage.abilities.keyword.FlyingAbility; +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 java.util.UUID; + +/** + * @author Susucr + */ +public final class StoicSphinx extends CardImpl { + + public StoicSphinx(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}{U}"); + + this.subtype.add(SubType.SPHINX); + this.power = new MageInt(5); + this.toughness = new MageInt(3); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Stoic Sphinx has hexproof as long as you haven't cast a spell this turn. + this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( + new GainAbilitySourceEffect(HexproofAbility.getInstance(), Duration.WhileOnBattlefield), + HaventCastSpellThisTurnCondition.instance, + "{this} has hexproof as long as you haven't cast a spell this turn" + ))); + } + + private StoicSphinx(final StoicSphinx card) { + super(card); + } + + @Override + public StoicSphinx copy() { + return new StoicSphinx(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/StopCold.java b/Mage.Sets/src/mage/cards/s/StopCold.java new file mode 100644 index 00000000000..9e4ba5d1425 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/StopCold.java @@ -0,0 +1,86 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.DontUntapInControllersUntapStepEnchantedEffect; +import mage.abilities.effects.common.TapEnchantedEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.abilities.keyword.FlashAbility; +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 java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class StopCold extends CardImpl { + + public StopCold(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{U}"); + + this.subtype.add(SubType.AURA); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // Enchant artifact or creature + TargetPermanent auraTarget = new TargetPermanent(StaticFilters.FILTER_PERMANENT_ARTIFACT_OR_CREATURE); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + this.addAbility(new EnchantAbility(auraTarget)); + + // When Stop Cold enters the battlefield, tap enchanted permanent. + this.addAbility(new EntersBattlefieldTriggeredAbility(new TapEnchantedEffect("permanent"))); + + // Enchanted permanent loses all abilities and doesn't untap during its controller's untap step. + Ability ability = new SimpleStaticAbility(new StopColdEffect()); + ability.addEffect(new DontUntapInControllersUntapStepEnchantedEffect() + .setText("and doesn't untap during its controller's untap step")); + this.addAbility(ability); + } + + private StopCold(final StopCold card) { + super(card); + } + + @Override + public StopCold copy() { + return new StopCold(this); + } +} + +class StopColdEffect extends ContinuousEffectImpl { + + StopColdEffect() { + super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.Benefit); + staticText = "enchanted permanent loses all abilities"; + } + + private StopColdEffect(final StopColdEffect effect) { + super(effect); + } + + @Override + public StopColdEffect copy() { + return new StopColdEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Optional.ofNullable(source.getSourcePermanentIfItStillExists(game)) + .map(Permanent::getAttachedTo) + .map(game::getPermanent) + .ifPresent(permanent -> permanent.removeAllAbilities(source.getSourceId(), game)); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/s/StructuralAssault.java b/Mage.Sets/src/mage/cards/s/StructuralAssault.java index 525a41bce55..5f90411bdff 100644 --- a/Mage.Sets/src/mage/cards/s/StructuralAssault.java +++ b/Mage.Sets/src/mage/cards/s/StructuralAssault.java @@ -3,8 +3,11 @@ package mage.cards.s; import java.util.UUID; import mage.abilities.Ability; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.DestroyAllEffect; +import mage.abilities.hint.ValueHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -23,6 +26,9 @@ import mage.watchers.Watcher; */ public final class StructuralAssault extends CardImpl { + private static final ValueHint hint = new ValueHint( + "Artifacts put into graveyards from the battlefield this turn", StructuralAssaultValue.instance); + public StructuralAssault(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{R}{R}"); @@ -30,6 +36,7 @@ public final class StructuralAssault extends CardImpl { this.getSpellAbility().addEffect(new DestroyAllEffect(StaticFilters.FILTER_PERMANENT_ARTIFACTS)); this.getSpellAbility().addEffect(new StructuralAssaultEffect()); this.getSpellAbility().addWatcher(new StructuralAssaultWatcher()); + this.getSpellAbility().addHint(hint); } private StructuralAssault(final StructuralAssault card) { @@ -42,7 +49,6 @@ public final class StructuralAssault extends CardImpl { } } -// CardsPutIntoGraveyardWatcher does not count tokens so custom watcher is needed. class StructuralAssaultWatcher extends Watcher { private int artifactsDied = 0; @@ -72,6 +78,30 @@ class StructuralAssaultWatcher extends Watcher { } } +// Copied from AnzragsRampageValue +enum StructuralAssaultValue implements DynamicValue { + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + StructuralAssaultWatcher watcher = game.getState().getWatcher(StructuralAssaultWatcher.class); + if (watcher == null) { + return 0; + } + return watcher.getArtifactsDied(); + } + + @Override + public StructuralAssaultValue copy() { + return instance; + } + + @Override + public String getMessage() { + return ""; + } +} + class StructuralAssaultEffect extends OneShotEffect { StructuralAssaultEffect() { diff --git a/Mage.Sets/src/mage/cards/s/StubbornBurrowfiend.java b/Mage.Sets/src/mage/cards/s/StubbornBurrowfiend.java new file mode 100644 index 00000000000..22d914d4118 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/StubbornBurrowfiend.java @@ -0,0 +1,122 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.MillCardsControllerEffect; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.hint.ValueHint; +import mage.abilities.keyword.SaddleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.util.CardUtil; +import mage.watchers.Watcher; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * @author Susucr + */ +public final class StubbornBurrowfiend extends CardImpl { + + private static final CardsInControllerGraveyardCount xValue = new CardsInControllerGraveyardCount(StaticFilters.FILTER_CARD_CREATURE); + private static final ValueHint hint = new ValueHint("Creature cards in your graveyard", xValue); + + public StubbornBurrowfiend(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); + + this.subtype.add(SubType.BADGER); + this.subtype.add(SubType.BEAST); + this.subtype.add(SubType.MOUNT); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Whenever Stubborn Burrowfiend becomes saddled for the first time each turn, mill two cards, then Stubborn Burrowfiend gets +X/+X until end of turn, where X is the number of creature cards in your graveyard. + Ability ability = new StubbornBurrowfiendTriggeredAbility(new MillCardsControllerEffect(2)); + ability.addEffect(new BoostSourceEffect(xValue, xValue, Duration.EndOfTurn) + .setText(", then {this} gets +X/+X until end of turn, where X is the number of creature cards in your graveyard")); + ability.addHint(hint); + this.addAbility(ability, new StubbornBurrowFiendWatcher()); + + // Saddle 2 + this.addAbility(new SaddleAbility(2)); + + } + + private StubbornBurrowfiend(final StubbornBurrowfiend card) { + super(card); + } + + @Override + public StubbornBurrowfiend copy() { + return new StubbornBurrowfiend(this); + } +} + +class StubbornBurrowFiendWatcher extends Watcher { + + // MOR -> number of times saddled this turn. + // Since Watcher are updated before triggers, we want to find 1 for the trigger. + private final Map saddledCount = new HashMap<>(); + + StubbornBurrowFiendWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() != GameEvent.EventType.MOUNT_SADDLED) { + return; + } + saddledCount.compute(new MageObjectReference(event.getTargetId(), game), CardUtil::setOrIncrementValue); + } + + public int timesSaddledThisTurn(MageObjectReference mor) { + return saddledCount.getOrDefault(mor, 0); + } + + @Override + public void reset() { + super.reset(); + saddledCount.clear(); + } +} + +class StubbornBurrowfiendTriggeredAbility extends TriggeredAbilityImpl { + + StubbornBurrowfiendTriggeredAbility(Effect effect) { + super(Zone.BATTLEFIELD, effect, false); + setTriggerPhrase("Whenever {this} becomes saddled for the first time each turn, "); + } + + private StubbornBurrowfiendTriggeredAbility(final StubbornBurrowfiendTriggeredAbility ability) { + super(ability); + } + + @Override + public StubbornBurrowfiendTriggeredAbility copy() { + return new StubbornBurrowfiendTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.MOUNT_SADDLED; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + StubbornBurrowFiendWatcher watcher = game.getState().getWatcher(StubbornBurrowFiendWatcher.class); + return watcher != null + && event.getSourceId().equals(this.getSourceId()) + && 1 == watcher.timesSaddledThisTurn(new MageObjectReference(event.getTargetId(), game)); + } +} diff --git a/Mage.Sets/src/mage/cards/s/StuffyDoll.java b/Mage.Sets/src/mage/cards/s/StuffyDoll.java index 4d8c45cf61a..28f4560693f 100644 --- a/Mage.Sets/src/mage/cards/s/StuffyDoll.java +++ b/Mage.Sets/src/mage/cards/s/StuffyDoll.java @@ -4,6 +4,7 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.AsEntersBattlefieldAbility; +import mage.abilities.common.DealtDamageToSourceTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.effects.OneShotEffect; @@ -39,7 +40,7 @@ public final class StuffyDoll extends CardImpl { // Stuffy Doll is indestructible. this.addAbility(IndestructibleAbility.getInstance()); // Whenever Stuffy Doll is dealt damage, it deals that much damage to the chosen player. - this.addAbility(new StuffyDollTriggeredAbility()); + this.addAbility(new DealtDamageToSourceTriggeredAbility(new StuffyDollEffect(), false)); // {T}: Stuffy Doll deals 1 damage to itself. this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new DamageSelfEffect(1), new TapSourceCost())); } @@ -54,51 +55,20 @@ public final class StuffyDoll extends CardImpl { } } -class StuffyDollTriggeredAbility extends TriggeredAbilityImpl { +class StuffyDollEffect extends OneShotEffect { - public StuffyDollTriggeredAbility() { - super(Zone.BATTLEFIELD, new StuffyDollGainLifeEffect()); - setTriggerPhrase("Whenever {this} is dealt damage, "); - } - - private StuffyDollTriggeredAbility(final StuffyDollTriggeredAbility effect) { - super(effect); - } - - @Override - public StuffyDollTriggeredAbility copy() { - return new StuffyDollTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (event.getTargetId().equals(this.sourceId)) { - this.getEffects().get(0).setValue("damageAmount", event.getAmount()); - return true; - } - return false; - } -} - -class StuffyDollGainLifeEffect extends OneShotEffect { - - StuffyDollGainLifeEffect() { + StuffyDollEffect() { super(Outcome.GainLife); staticText = "it deals that much damage to the chosen player"; } - private StuffyDollGainLifeEffect(final StuffyDollGainLifeEffect effect) { + private StuffyDollEffect(final StuffyDollEffect effect) { super(effect); } @Override - public StuffyDollGainLifeEffect copy() { - return new StuffyDollGainLifeEffect(this); + public StuffyDollEffect copy() { + return new StuffyDollEffect(this); } @Override @@ -106,7 +76,7 @@ class StuffyDollGainLifeEffect extends OneShotEffect { UUID playerId = (UUID) game.getState().getValue(source.getSourceId() + "_player"); Player player = game.getPlayer(playerId); if (player != null && player.canRespond()) { - player.damage((Integer) this.getValue("damageAmount"), source.getSourceId(), source, game); + player.damage((Integer) this.getValue("damage"), source.getSourceId(), source, game); } return true; } diff --git a/Mage.Sets/src/mage/cards/s/SunDroplet.java b/Mage.Sets/src/mage/cards/s/SunDroplet.java index 2d53bef3c4e..3830f941642 100644 --- a/Mage.Sets/src/mage/cards/s/SunDroplet.java +++ b/Mage.Sets/src/mage/cards/s/SunDroplet.java @@ -65,7 +65,7 @@ class SunDropletTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PLAYER; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PLAYER; } @Override diff --git a/Mage.Sets/src/mage/cards/s/SunkenCitadel.java b/Mage.Sets/src/mage/cards/s/SunkenCitadel.java index 4f50a1a809f..e67ea38f2de 100644 --- a/Mage.Sets/src/mage/cards/s/SunkenCitadel.java +++ b/Mage.Sets/src/mage/cards/s/SunkenCitadel.java @@ -4,13 +4,9 @@ import mage.ConditionalMana; import mage.MageObject; import mage.Mana; import mage.abilities.Ability; -import mage.abilities.StaticAbility; +import mage.abilities.common.EntersBattlefieldTappedAsItEntersChooseColorAbility; import mage.abilities.condition.Condition; import mage.abilities.costs.common.TapSourceCost; -import mage.abilities.effects.Effect; -import mage.abilities.effects.EntersBattlefieldEffect; -import mage.abilities.effects.common.ChooseColorEffect; -import mage.abilities.effects.common.TapSourceEffect; import mage.abilities.effects.mana.AddConditionalManaChosenColorEffect; import mage.abilities.effects.mana.AddManaChosenColorEffect; import mage.abilities.mana.SimpleManaAbility; @@ -18,7 +14,6 @@ import mage.abilities.mana.builder.ConditionalManaBuilder; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.Zone; import mage.game.Game; @@ -36,7 +31,7 @@ public final class SunkenCitadel extends CardImpl { this.subtype.add(SubType.CAVE); // Sunken Citadel enters the battlefield tapped. As it enters, choose a color. - this.addAbility(new SunkenCitadelEntersBattlefieldAbility()); + this.addAbility(new EntersBattlefieldTappedAsItEntersChooseColorAbility()); // {T}: Add one mana of the chosen color. this.addAbility(new SimpleManaAbility(Zone.BATTLEFIELD, new AddManaChosenColorEffect(), new TapSourceCost())); @@ -55,40 +50,6 @@ public final class SunkenCitadel extends CardImpl { } } -class SunkenCitadelEntersBattlefieldAbility extends StaticAbility { - - public SunkenCitadelEntersBattlefieldAbility() { - super(Zone.ALL, new EntersBattlefieldEffect(new TapSourceEffect(true))); - this.addEffect(new ChooseColorEffect(Outcome.Benefit)); - } - - private SunkenCitadelEntersBattlefieldAbility(final SunkenCitadelEntersBattlefieldAbility ability) { - super(ability); - } - - @Override - public SunkenCitadelEntersBattlefieldAbility copy() { - return new SunkenCitadelEntersBattlefieldAbility(this); - } - - @Override - public void addEffect(Effect effect) { - if (!getEffects().isEmpty()) { - Effect entersEffect = this.getEffects().get(0); - if (entersEffect instanceof EntersBattlefieldEffect) { - ((EntersBattlefieldEffect) entersEffect).addEffect(effect); - return; - } - } - super.addEffect(effect); - } - - @Override - public String getRule() { - return "{this} enters the battlefield tapped. As it enters, choose a color."; - } -} - class SunkenCitadelManaBuilder extends ConditionalManaBuilder { @Override diff --git a/Mage.Sets/src/mage/cards/s/SwarmbornGiant.java b/Mage.Sets/src/mage/cards/s/SwarmbornGiant.java index 11ab1b5e5ba..3cd0f13715c 100644 --- a/Mage.Sets/src/mage/cards/s/SwarmbornGiant.java +++ b/Mage.Sets/src/mage/cards/s/SwarmbornGiant.java @@ -1,7 +1,6 @@ package mage.cards.s; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; @@ -15,13 +14,14 @@ import mage.abilities.keyword.ReachAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.Duration; +import mage.constants.SubType; import mage.constants.Zone; import mage.game.Game; -import mage.game.events.DamagedEvent; +import mage.game.events.DamagedBatchForOnePlayerEvent; import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; + +import java.util.UUID; /** * @@ -79,14 +79,14 @@ class SwarmbornGiantTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PLAYER; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PLAYER; } @Override public boolean checkTrigger(GameEvent event, Game game) { - if (event.getTargetId().equals(this.getControllerId())) { - DamagedEvent damagedEvent = (DamagedEvent) event; - return damagedEvent.isCombatDamage(); + DamagedBatchForOnePlayerEvent dEvent = (DamagedBatchForOnePlayerEvent) event; + if (dEvent.getTargetId().equals(this.getControllerId())) { + return dEvent.isCombatDamage() && dEvent.getAmount() > 0; } return false; } diff --git a/Mage.Sets/src/mage/cards/s/SwordOfWealthAndPower.java b/Mage.Sets/src/mage/cards/s/SwordOfWealthAndPower.java new file mode 100644 index 00000000000..682e5794a5a --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SwordOfWealthAndPower.java @@ -0,0 +1,61 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.common.DealsDamageToAPlayerAttachedTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.common.delayed.CopyNextSpellDelayedTriggeredAbility; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.continuous.BoostEquippedEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.keyword.EquipAbility; +import mage.abilities.keyword.ProtectionAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AttachmentType; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.common.FilterInstantOrSorceryCard; +import mage.game.permanent.token.TreasureToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SwordOfWealthAndPower extends CardImpl { + + private static final FilterCard filter = new FilterInstantOrSorceryCard("instants and from sorceries"); + + public SwordOfWealthAndPower(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}"); + + this.subtype.add(SubType.EQUIPMENT); + + // Equipped creature gets +2/+2 and has protection from instants and from sorceries. + Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(2, 2)); + ability.addEffect(new GainAbilityAttachedEffect(new ProtectionAbility(filter), AttachmentType.EQUIPMENT) + .setText("and has protection from instants and from sorceries")); + this.addAbility(ability); + + // Whenever equipped creature deals combat damage to a player, create a Treasure token. When you next cast an instant or sorcery spell this turn, copy that spell. You may choose new targets for the copy. + ability = new DealsDamageToAPlayerAttachedTriggeredAbility( + new CreateTokenEffect(new TreasureToken()), "equipped", false + ); + ability.addEffect(new CreateDelayedTriggeredAbilityEffect(new CopyNextSpellDelayedTriggeredAbility())); + this.addAbility(ability); + + // Equip {2} + this.addAbility(new EquipAbility(2)); + } + + private SwordOfWealthAndPower(final SwordOfWealthAndPower card) { + super(card); + } + + @Override + public SwordOfWealthAndPower copy() { + return new SwordOfWealthAndPower(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SylvanPrimordial.java b/Mage.Sets/src/mage/cards/s/SylvanPrimordial.java index 2cdf67038a1..5068a9c92a1 100644 --- a/Mage.Sets/src/mage/cards/s/SylvanPrimordial.java +++ b/Mage.Sets/src/mage/cards/s/SylvanPrimordial.java @@ -15,14 +15,12 @@ import mage.constants.SubType; import mage.filter.FilterPermanent; import mage.filter.common.FilterLandCard; 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.Target; import mage.target.TargetPermanent; import mage.target.common.TargetCardInLibrary; -import mage.target.targetadjustment.TargetAdjuster; +import mage.target.targetadjustment.EachOpponentPermanentTargetsAdjuster; import java.util.UUID; @@ -31,6 +29,11 @@ import java.util.UUID; */ public final class SylvanPrimordial extends CardImpl { + private static final FilterPermanent filter = new FilterPermanent("noncreature permanent"); + + static { + filter.add(Predicates.not(CardType.CREATURE.getPredicate())); + } public SylvanPrimordial(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{G}{G}"); this.subtype.add(SubType.AVATAR); @@ -43,7 +46,7 @@ public final class SylvanPrimordial extends CardImpl { // When Sylvan Primordial enters the battlefield, for each opponent, destroy target noncreature permanent that player controls. For each permanent destroyed this way, search your library for a Forest card and put that card onto the battlefield tapped. Then shuffle your library. Ability ability = new EntersBattlefieldTriggeredAbility(new SylvanPrimordialEffect(), false); - ability.setTargetAdjuster(SylvanPrimordialAdjuster.instance); + ability.setTargetAdjuster(new EachOpponentPermanentTargetsAdjuster(new TargetPermanent(filter))); this.addAbility(ability); } @@ -57,25 +60,6 @@ public final class SylvanPrimordial extends CardImpl { } } -enum SylvanPrimordialAdjuster implements TargetAdjuster { - instance; - - @Override - public void adjustTargets(Ability ability, Game game) { - ability.getTargets().clear(); - for (UUID opponentId : game.getOpponents(ability.getControllerId())) { - Player opponent = game.getPlayer(opponentId); - if (opponent != null) { - FilterPermanent filter = new FilterPermanent("noncreature permanent from opponent " + opponent.getLogName()); - filter.add(new ControllerIdPredicate(opponentId)); - filter.add(Predicates.not(CardType.CREATURE.getPredicate())); - TargetPermanent target = new TargetPermanent(0, 1, filter, false); - ability.addTarget(target); - } - } - } -} - class SylvanPrimordialEffect extends OneShotEffect { private static final FilterLandCard filterForest = new FilterLandCard("Forest"); diff --git a/Mage.Sets/src/mage/cards/t/T45PowerArmor.java b/Mage.Sets/src/mage/cards/t/T45PowerArmor.java new file mode 100644 index 00000000000..381484482af --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/T45PowerArmor.java @@ -0,0 +1,116 @@ +package mage.cards.t; + +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.common.PayEnergyCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.DontUntapInControllersUntapStepEnchantedEffect; +import mage.abilities.effects.common.continuous.BoostEquippedEffect; +import mage.abilities.effects.common.counter.GetEnergyCountersControllerEffect; +import mage.abilities.keyword.EquipAbility; +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.SubType; +import mage.constants.TargetController; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; + +import java.util.*; + +/** + * + * @author justinjohnson14 + */ +public final class T45PowerArmor extends CardImpl { + + public T45PowerArmor(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); + + this.subtype.add(SubType.EQUIPMENT); + + // When T-45 Power Armor enters the battlefield, you get {E}{E}. + this.addAbility(new EntersBattlefieldTriggeredAbility(new GetEnergyCountersControllerEffect(2), false)); + // Equipped creature gets +3/+3 and doesn't untap during its controller's untap step. + Ability ability = (new SimpleStaticAbility(new BoostEquippedEffect(3,3))); + ability.addEffect(new DontUntapInControllersUntapStepEnchantedEffect().setText("and doesn't untap during its controller's untap step")); + this.addAbility(ability); + + // At the beginning of your upkeep, you may pay {E}. If you do, untap equipped creature, then put your choice of a menace, trample or lifelink counter on it. + this.addAbility(new BeginningOfUpkeepTriggeredAbility( + new DoIfCostPaid( + new T45PowerArmorEffect(), + new PayEnergyCost(1) + ), + TargetController.YOU, + false + )); + + // Equip {3} + this.addAbility(new EquipAbility(3, false)); + } + + private T45PowerArmor(final T45PowerArmor card) { + super(card); + } + + @Override + public T45PowerArmor copy() { + return new T45PowerArmor(this); + } +} + +class T45PowerArmorEffect extends OneShotEffect { + + private static final Set choices = new LinkedHashSet<>(Arrays.asList("Menace", "Trample", "Lifelink")); + + T45PowerArmorEffect() { + super(Outcome.BoostCreature); + staticText = "untap equipped creature, then put your choice of a menace, trample, or lifelink counter on it"; + } + + private T45PowerArmorEffect(T45PowerArmorEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Permanent attachment = source.getSourcePermanentIfItStillExists(game); + if (player == null || attachment == null) { + return false; + } + Permanent creature = game.getPermanent(attachment.getAttachedTo()); + if (creature == null) { + return false; + } + + creature.untap(game); + + Choice choice = new ChoiceImpl(true); + choice.setMessage("Choose menace, trample, or lifelink"); + choice.setChoices(choices); + player.choose(outcome, choice, game); + String chosen = choice.getChoice(); + if (chosen != null) { + creature.addCounters(CounterType.findByName( + chosen.toLowerCase(Locale.ENGLISH) + ).createInstance(), source.getControllerId(), source, game); + } + + return true; + } + + @Override + public T45PowerArmorEffect copy() { + return new T45PowerArmorEffect(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TaiiWakeenPerfectShot.java b/Mage.Sets/src/mage/cards/t/TaiiWakeenPerfectShot.java new file mode 100644 index 00000000000..8007bf09ed2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TaiiWakeenPerfectShot.java @@ -0,0 +1,127 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.game.events.DamageEvent; +import mage.game.events.DamagedEvent; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TaiiWakeenPerfectShot extends CardImpl { + + public TaiiWakeenPerfectShot(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{R}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.MERCENARY); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Whenever a source you control deals noncombat damage to a creature equal to that creature's toughness, draw a card. + this.addAbility(new TaiiWakeenPerfectShotTriggeredAbility()); + + // {X}, {T}: If a source you control would deal noncombat damage to a permanent or player this turn, it deals that much damage plus X instead. + Ability ability = new SimpleActivatedAbility(new TaiiWakeenPerfectShotEffect(), new ManaCostsImpl<>("{X}")); + ability.addCost(new TapSourceCost()); + this.addAbility(ability); + } + + private TaiiWakeenPerfectShot(final TaiiWakeenPerfectShot card) { + super(card); + } + + @Override + public TaiiWakeenPerfectShot copy() { + return new TaiiWakeenPerfectShot(this); + } +} + +class TaiiWakeenPerfectShotTriggeredAbility extends TriggeredAbilityImpl { + + TaiiWakeenPerfectShotTriggeredAbility() { + super(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1)); + setTriggerPhrase("Whenever a source you control deals noncombat damage to a creature equal to that creature's toughness, "); + } + + private TaiiWakeenPerfectShotTriggeredAbility(final TaiiWakeenPerfectShotTriggeredAbility ability) { + super(ability); + } + + @Override + public TaiiWakeenPerfectShotTriggeredAbility copy() { + return new TaiiWakeenPerfectShotTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + Permanent permanent = game.getPermanent(event.getTargetId()); + return !((DamagedEvent) event).isCombatDamage() + && isControlledBy(game.getControllerId(event.getSourceId())) + && permanent != null + && permanent.isCreature(game) + && event.getAmount() == permanent.getToughness().getValue(); + } +} + +class TaiiWakeenPerfectShotEffect extends ReplacementEffectImpl { + + TaiiWakeenPerfectShotEffect() { + super(Duration.EndOfTurn, Outcome.Benefit); + staticText = "if a source you control would deal noncombat damage to " + + "a permanent or player this turn, it deals that much damage plus X instead"; + } + + private TaiiWakeenPerfectShotEffect(final TaiiWakeenPerfectShotEffect effect) { + super(effect); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + switch (event.getType()) { + case DAMAGE_PERMANENT: + case DAMAGE_PLAYER: + return true; + default: + return false; + } + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + return !((DamageEvent) event).isCombatDamage() + && source.isControlledBy(game.getControllerId(event.getSourceId())); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + event.setAmount(CardUtil.overflowInc(event.getAmount(), source.getManaCostsToPay().getX())); + return false; + } + + @Override + public TaiiWakeenPerfectShotEffect copy() { + return new TaiiWakeenPerfectShotEffect(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TakeForARide.java b/Mage.Sets/src/mage/cards/t/TakeForARide.java new file mode 100644 index 00000000000..f295722e9ab --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TakeForARide.java @@ -0,0 +1,55 @@ +package mage.cards.t; + +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.CommittedCrimeCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.common.UntapTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.effects.common.continuous.GainControlTargetEffect; +import mage.abilities.keyword.FlashAbility; +import mage.abilities.keyword.HasteAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Zone; +import mage.target.common.TargetCreaturePermanent; +import mage.watchers.common.CommittedCrimeWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TakeForARide extends CardImpl { + + public TakeForARide(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{R}"); + + // Take for a Ride has flash as long as you've committed a crime this turn. + this.addAbility(new SimpleStaticAbility( + Zone.ALL, + new ConditionalContinuousEffect(new GainAbilitySourceEffect( + FlashAbility.getInstance(), Duration.WhileOnBattlefield, true + ), CommittedCrimeCondition.instance, "{this} has flash as long as you've committed a crime this turn") + ).setRuleAtTheTop(true).addHint(CommittedCrimeCondition.getHint()), new CommittedCrimeWatcher()); + + // Gain control of target creature until end of turn. Untap that creature. It gains haste until end of turn. + this.getSpellAbility().addEffect(new GainControlTargetEffect(Duration.EndOfTurn)); + this.getSpellAbility().addEffect(new UntapTargetEffect().setText("Untap that creature")); + this.getSpellAbility().addEffect(new GainAbilityTargetEffect( + HasteAbility.getInstance(), Duration.EndOfTurn + ).setText("It gains haste until end of turn.")); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + } + + private TakeForARide(final TakeForARide card) { + super(card); + } + + @Override + public TakeForARide copy() { + return new TakeForARide(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TakeTheFall.java b/Mage.Sets/src/mage/cards/t/TakeTheFall.java new file mode 100644 index 00000000000..b6a259d69fa --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TakeTheFall.java @@ -0,0 +1,61 @@ +package mage.cards.t; + +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.hint.ConditionHint; +import mage.abilities.hint.Hint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.filter.FilterPermanent; +import mage.filter.predicate.mageobject.OutlawPredicate; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class TakeTheFall extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("an outlaw"); + + static { + filter.add(OutlawPredicate.instance); + } + + private static final Condition condition = new PermanentsOnTheBattlefieldCondition(filter, true); + private static final Hint hint = new ConditionHint(condition, "you control an outlaw"); + + public TakeTheFall(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{U}"); + + // Target creature gets -1/-0 until end of turn. It gets -4/-0 until end of turn instead if you control an outlaw. + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + this.getSpellAbility().addEffect(new ConditionalContinuousEffect( + new BoostTargetEffect(-4, 0, Duration.EndOfTurn), + new BoostTargetEffect(-1, 0, Duration.EndOfTurn), + condition, + "Target creature gets -1/-0 until end of turn. It gets -4/-0 until end of turn instead if you control an outlaw.")); + this.getSpellAbility().addHint(hint); + + // Draw a card. + this.getSpellAbility().addEffect( + new DrawCardSourceControllerEffect(1) + .concatBy("
") + ); + } + + private TakeTheFall(final TakeTheFall card) { + super(card); + } + + @Override + public TakeTheFall copy() { + return new TakeTheFall(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TangleweaveArmor.java b/Mage.Sets/src/mage/cards/t/TangleweaveArmor.java index b8676df6e2d..d3a494c7ebb 100644 --- a/Mage.Sets/src/mage/cards/t/TangleweaveArmor.java +++ b/Mage.Sets/src/mage/cards/t/TangleweaveArmor.java @@ -1,18 +1,17 @@ package mage.cards.t; -import mage.MageObject; -import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.mana.GenericManaCost; -import mage.abilities.dynamicvalue.DynamicValue; -import mage.abilities.effects.Effect; +import mage.abilities.dynamicvalue.common.CommanderGreatestManaValue; import mage.abilities.effects.common.continuous.BoostEquippedEffect; import mage.abilities.keyword.EquipAbility; import mage.abilities.keyword.LivingWeaponAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; -import mage.game.Game; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.Zone; import java.util.UUID; @@ -30,7 +29,7 @@ public final class TangleweaveArmor extends CardImpl { // Equipped creature gets +X/+X, where X is the greatest mana value among your commanders this.addAbility(new SimpleStaticAbility( - Zone.BATTLEFIELD, new BoostEquippedEffect(TangleweaveArmorDynamicValue.instance, TangleweaveArmorDynamicValue.instance) + Zone.BATTLEFIELD, new BoostEquippedEffect(CommanderGreatestManaValue.instance, CommanderGreatestManaValue.instance) )); // Equip {4} @@ -46,32 +45,3 @@ public final class TangleweaveArmor extends CardImpl { return new TangleweaveArmor(this); } } - -enum TangleweaveArmorDynamicValue implements DynamicValue { - instance; - - @Override - public int calculate(Game game, Ability sourceAbility, Effect effect) { - return game.getCommanderCardsFromAnyZones( - game.getPlayer(sourceAbility.getControllerId()), CommanderCardType.ANY, Zone.ALL) - .stream() - .mapToInt(MageObject::getManaValue) - .max() - .orElse(0); - } - - @Override - public TangleweaveArmorDynamicValue copy() { - return this; - } - - @Override - public String toString() { - return "X"; - } - - @Override - public String getMessage() { - return "the greatest mana value among your commanders"; - } -} diff --git a/Mage.Sets/src/mage/cards/t/TarnationVista.java b/Mage.Sets/src/mage/cards/t/TarnationVista.java new file mode 100644 index 00000000000..d2f684026e1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TarnationVista.java @@ -0,0 +1,112 @@ +package mage.cards.t; + +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTappedAsItEntersChooseColorAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.mana.AddManaChosenColorEffect; +import mage.abilities.effects.mana.ManaEffect; +import mage.abilities.mana.ActivatedManaAbilityImpl; +import mage.abilities.mana.SimpleManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.permanent.Permanent; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class TarnationVista extends CardImpl { + + public TarnationVista(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + // Tarnation Vista enters the battlefield tapped. As it enters, choose a color. + this.addAbility(new EntersBattlefieldTappedAsItEntersChooseColorAbility()); + + // {T}: Add one mana of the chosen color. + this.addAbility(new SimpleManaAbility(Zone.BATTLEFIELD, new AddManaChosenColorEffect(), new TapSourceCost())); + + // {1}, {T}: For each color among monocolored permanents you control, add one mana of that color. + this.addAbility(new TarnationVistaManaAbility()); + } + + private TarnationVista(final TarnationVista card) { + super(card); + } + + @Override + public TarnationVista copy() { + return new TarnationVista(this); + } +} + +class TarnationVistaManaAbility extends ActivatedManaAbilityImpl { + + TarnationVistaManaAbility() { + super(Zone.BATTLEFIELD, new TarnationVistaEffect(), new GenericManaCost(1)); + this.addCost(new TapSourceCost()); + } + + private TarnationVistaManaAbility(final TarnationVistaManaAbility ability) { + super(ability); + } + + @Override + public TarnationVistaManaAbility copy() { + return new TarnationVistaManaAbility(this); + } + +} + +// Inspired by Bloom Tender +class TarnationVistaEffect extends ManaEffect { + + TarnationVistaEffect() { + super(); + staticText = "For each color among monocolored permanents you control, add one mana of that color"; + } + + private TarnationVistaEffect(final TarnationVistaEffect effect) { + super(effect); + } + + @Override + public TarnationVistaEffect copy() { + return new TarnationVistaEffect(this); + } + + @Override + public Mana produceMana(Game game, Ability source) { + Mana mana = new Mana(); + if (game == null) { + return mana; + } + for (Permanent permanent : game.getBattlefield().getAllActivePermanents(source.getControllerId())) { + if (permanent.getColor(game).getColorCount() != 1) { + continue; + } + if (mana.getBlack() == 0 && permanent.getColor(game).isBlack()) { + mana.increaseBlack(); + } + if (mana.getBlue() == 0 && permanent.getColor(game).isBlue()) { + mana.increaseBlue(); + } + if (mana.getRed() == 0 && permanent.getColor(game).isRed()) { + mana.increaseRed(); + } + if (mana.getGreen() == 0 && permanent.getColor(game).isGreen()) { + mana.increaseGreen(); + } + if (mana.getWhite() == 0 && permanent.getColor(game).isWhite()) { + mana.increaseWhite(); + } + } + return mana; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TemptedByTheOriq.java b/Mage.Sets/src/mage/cards/t/TemptedByTheOriq.java index 8eff4f25d7c..56429d94364 100644 --- a/Mage.Sets/src/mage/cards/t/TemptedByTheOriq.java +++ b/Mage.Sets/src/mage/cards/t/TemptedByTheOriq.java @@ -1,20 +1,15 @@ package mage.cards.t; -import mage.abilities.Ability; import mage.abilities.effects.common.continuous.GainControlTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.ComparisonType; import mage.constants.Duration; -import mage.filter.FilterPermanent; import mage.filter.common.FilterCreatureOrPlaneswalkerPermanent; import mage.filter.predicate.mageobject.ManaValuePredicate; -import mage.filter.predicate.permanent.ControllerIdPredicate; -import mage.game.Game; -import mage.players.Player; import mage.target.TargetPermanent; -import mage.target.targetadjustment.TargetAdjuster; +import mage.target.targetadjustment.EachOpponentPermanentTargetsAdjuster; import mage.target.targetpointer.EachTargetPointer; import java.util.UUID; @@ -23,7 +18,11 @@ import java.util.UUID; * @author TheElk801 */ public final class TemptedByTheOriq extends CardImpl { + private static final FilterCreatureOrPlaneswalkerPermanent filter = new FilterCreatureOrPlaneswalkerPermanent("creature or planeswalker with mana value 3 or less"); + static { + filter.add(new ManaValuePredicate(ComparisonType.OR_LESS, 3)); + } public TemptedByTheOriq(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{U}{U}{U}"); @@ -32,7 +31,7 @@ public final class TemptedByTheOriq extends CardImpl { .setTargetPointer(new EachTargetPointer()) .setText("for each opponent, gain control of up to one target creature " + "or planeswalker that player controls with mana value 3 or less")); - this.getSpellAbility().setTargetAdjuster(TemptedByTheOriqAdjuster.instance); + this.getSpellAbility().setTargetAdjuster(new EachOpponentPermanentTargetsAdjuster(new TargetPermanent(0, 1, filter))); } private TemptedByTheOriq(final TemptedByTheOriq card) { @@ -44,24 +43,3 @@ public final class TemptedByTheOriq extends CardImpl { return new TemptedByTheOriq(this); } } - -enum TemptedByTheOriqAdjuster implements TargetAdjuster { - instance; - - @Override - public void adjustTargets(Ability ability, Game game) { - ability.getTargets().clear(); - for (UUID opponentId : game.getOpponents(ability.getControllerId())) { - Player opponent = game.getPlayer(opponentId); - if (opponent == null) { - continue; - } - FilterPermanent filter = new FilterCreatureOrPlaneswalkerPermanent( - "creature or planeswalker " + opponent.getName() + " controls with mana value 3 or less" - ); - filter.add(new ControllerIdPredicate(opponentId)); - filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, 4)); - ability.addTarget(new TargetPermanent(0, 1, filter, false)); - } - } -} diff --git a/Mage.Sets/src/mage/cards/t/TerritoryForge.java b/Mage.Sets/src/mage/cards/t/TerritoryForge.java new file mode 100644 index 00000000000..d0fb868573c --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TerritoryForge.java @@ -0,0 +1,100 @@ +package mage.cards.t; + +import mage.abilities.Ability; +import mage.abilities.ActivatedAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.CastFromEverywhereSourceCondition; +import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.predicate.Predicates; +import mage.game.ExileZone; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class TerritoryForge extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("artifact or land"); + + static { + filter.add(Predicates.or(CardType.ARTIFACT.getPredicate(), CardType.LAND.getPredicate())); + } + + public TerritoryForge(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{4}{R}"); + + // When Territory Forge enters the battlefield, if you cast it, exile target artifact or land. + Ability ability = new ConditionalInterveningIfTriggeredAbility( + new EntersBattlefieldTriggeredAbility(new ExileTargetEffect().setToSourceExileZone(true)), + CastFromEverywhereSourceCondition.instance, + "When {this} enters the battlefield, if you cast it, exile target artifact or land." + ); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + + // Territory Forge has all activated abilities of the exiled card. + this.addAbility(new SimpleStaticAbility(new TerritoryForgeStaticEffect())); + } + + private TerritoryForge(final TerritoryForge card) { + super(card); + } + + @Override + public TerritoryForge copy() { + return new TerritoryForge(this); + } +} + +// Inspired by Dark Impostor +class TerritoryForgeStaticEffect extends ContinuousEffectImpl { + + TerritoryForgeStaticEffect() { + super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.Benefit); + staticText = "{this} has all activated abilities of the exiled card"; + } + + private TerritoryForgeStaticEffect(final TerritoryForgeStaticEffect effect) { + super(effect); + } + + @Override + public TerritoryForgeStaticEffect copy() { + return new TerritoryForgeStaticEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + if (permanent == null) { + return false; + } + UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), permanent.getZoneChangeCounter(game)); + ExileZone exileZone = game.getExile().getExileZone(exileId); + if (exileZone == null || exileZone.isEmpty()) { + return false; + } + for (Card card : exileZone.getCards(game)) { + for (Ability ability : card.getAbilities(game)) { + if (ability.getAbilityType() == AbilityType.ACTIVATED || ability.getAbilityType() == AbilityType.MANA) { + ActivatedAbility copyAbility = (ActivatedAbility) ability.copy(); + permanent.addAbility(copyAbility, source.getSourceId(), game, true); + } + } + } + return true; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/t/TheBalrogOfMoria.java b/Mage.Sets/src/mage/cards/t/TheBalrogOfMoria.java index f977cb1cded..ed6097f131a 100644 --- a/Mage.Sets/src/mage/cards/t/TheBalrogOfMoria.java +++ b/Mage.Sets/src/mage/cards/t/TheBalrogOfMoria.java @@ -1,7 +1,6 @@ package mage.cards.t; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.CycleTriggeredAbility; import mage.abilities.common.DiesSourceTriggeredAbility; import mage.abilities.common.delayed.ReflexiveTriggeredAbility; @@ -18,14 +17,9 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.SuperType; -import mage.filter.FilterPermanent; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.permanent.ControllerIdPredicate; -import mage.game.Game; import mage.game.permanent.token.TreasureToken; -import mage.players.Player; -import mage.target.TargetPermanent; -import mage.target.targetadjustment.TargetAdjuster; +import mage.target.common.TargetCreaturePermanent; +import mage.target.targetadjustment.EachOpponentPermanentTargetsAdjuster; import mage.target.targetpointer.EachTargetPointer; import java.util.UUID; @@ -57,7 +51,7 @@ public final class TheBalrogOfMoria extends CardImpl { .setText("for each opponent, exile up to one target creature that player controls."), false ); - reflexiveAbility.setTargetAdjuster(TheBalrogOfMoriaAdjuster.instance); + reflexiveAbility.setTargetAdjuster(new EachOpponentPermanentTargetsAdjuster(new TargetCreaturePermanent(0,1))); this.addAbility(new DiesSourceTriggeredAbility( new DoWhenCostPaid( @@ -83,21 +77,3 @@ public final class TheBalrogOfMoria extends CardImpl { return new TheBalrogOfMoria(this); } } - -enum TheBalrogOfMoriaAdjuster implements TargetAdjuster { - instance; - - @Override - public void adjustTargets(Ability ability, Game game) { - ability.getTargets().clear(); - for (UUID opponentId : game.getOpponents(ability.getControllerId())) { - Player opponent = game.getPlayer(opponentId); - if (opponent == null) { - continue; - } - FilterPermanent filter = new FilterCreaturePermanent("creature controlled by " + opponent.getLogName()); - filter.add(new ControllerIdPredicate(opponentId)); - ability.addTarget(new TargetPermanent(0, 1, filter, false)); - } - } -} diff --git a/Mage.Sets/src/mage/cards/t/TheBelligerent.java b/Mage.Sets/src/mage/cards/t/TheBelligerent.java index dae76d87340..6f05a6f5a7f 100644 --- a/Mage.Sets/src/mage/cards/t/TheBelligerent.java +++ b/Mage.Sets/src/mage/cards/t/TheBelligerent.java @@ -5,7 +5,7 @@ import mage.abilities.Ability; import mage.abilities.common.AttacksTriggeredAbility; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; -import mage.abilities.effects.common.continuous.PlayTheTopCardEffect; +import mage.abilities.effects.common.continuous.PlayFromTopOfLibraryEffect; import mage.abilities.keyword.CrewAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -32,10 +32,8 @@ public final class TheBelligerent extends CardImpl { // Whenever The Belligerent attacks, create a Treasure token. Until end of turn, you may look at the top card of your library any time, and you may play lands and cast spells from the top of your library. Ability ability = new AttacksTriggeredAbility(new CreateTokenEffect(new TreasureToken())); - ability.addEffect(new LookAtTopCardOfLibraryAnyTimeEffect(TargetController.YOU, Duration.EndOfTurn)); - ability.addEffect(new PlayTheTopCardEffect( - TargetController.YOU, filter, false - ).concatBy(", and")); + ability.addEffect(new LookAtTopCardOfLibraryAnyTimeEffect(Duration.EndOfTurn)); + ability.addEffect(new PlayFromTopOfLibraryEffect(filter).setDuration(Duration.EndOfTurn).concatBy(", and")); this.addAbility(ability); // Crew 3 diff --git a/Mage.Sets/src/mage/cards/t/TheGitrogRavenousRide.java b/Mage.Sets/src/mage/cards/t/TheGitrogRavenousRide.java new file mode 100644 index 00000000000..27e4b14ee58 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheGitrogRavenousRide.java @@ -0,0 +1,114 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.SaddleAbility; +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.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.SaddledSourceThisTurnPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInHand; +import mage.target.common.TargetSacrifice; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TheGitrogRavenousRide extends CardImpl { + + public TheGitrogRavenousRide(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.FROG); + this.subtype.add(SubType.HORROR); + this.subtype.add(SubType.MOUNT); + this.power = new MageInt(6); + this.toughness = new MageInt(5); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // Whenever The Gitrog, Ravenous Ride deals combat damage to a player, you may sacrifice a creature that saddled it this turn. If you do, draw X cards, then put up to X land cards from your hand onto the battlefield tapped, where X is the sacrificed creature's power. + this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility(new TheGitrogRavenousRideEffect(), false)); + + // Saddle 1 + this.addAbility(new SaddleAbility(1)); + } + + private TheGitrogRavenousRide(final TheGitrogRavenousRide card) { + super(card); + } + + @Override + public TheGitrogRavenousRide copy() { + return new TheGitrogRavenousRide(this); + } +} + +class TheGitrogRavenousRideEffect extends OneShotEffect { + + private static final FilterPermanent filter = new FilterCreaturePermanent("creature that saddled it this turn"); + + static { + filter.add(SaddledSourceThisTurnPredicate.instance); + } + + TheGitrogRavenousRideEffect() { + super(Outcome.Benefit); + staticText = "you may sacrifice a creature that saddled it this turn. " + + "If you do, draw X cards, then put up to X land cards from your hand onto the battlefield tapped, " + + "where X is the sacrificed creature's power"; + } + + private TheGitrogRavenousRideEffect(final TheGitrogRavenousRideEffect effect) { + super(effect); + } + + @Override + public TheGitrogRavenousRideEffect copy() { + return new TheGitrogRavenousRideEffect(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, 1, filter); + player.choose(outcome, target, source, game); + Permanent permanent = game.getPermanent(target.getFirstTarget()); + if (permanent == null) { + return false; + } + int power = permanent.getPower().getValue(); + permanent.sacrifice(source, game); + player.drawCards(power, source, game); + TargetCard targetCard = new TargetCardInHand(0, power, StaticFilters.FILTER_CARD_LANDS); + player.choose(outcome, player.getHand(), targetCard, source, game); + Cards cards = new CardsImpl(targetCard.getTargets()); + player.moveCards( + cards.getCards(game), Zone.BATTLEFIELD, source, game, + true, false, false, null + ); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TheHorusHeresy.java b/Mage.Sets/src/mage/cards/t/TheHorusHeresy.java index f8ce4d1471b..cb67f709700 100644 --- a/Mage.Sets/src/mage/cards/t/TheHorusHeresy.java +++ b/Mage.Sets/src/mage/cards/t/TheHorusHeresy.java @@ -17,13 +17,12 @@ import mage.filter.StaticFilters; import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.common.FilterCreaturePermanent; 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.TargetCreaturePermanent; -import mage.target.targetadjustment.TargetAdjuster; +import mage.target.targetadjustment.EachOpponentPermanentTargetsAdjuster; import mage.target.targetpointer.EachTargetPointer; import java.util.HashSet; @@ -37,9 +36,11 @@ public final class TheHorusHeresy extends CardImpl { private static final FilterPermanent filter = new FilterControlledCreaturePermanent("creature you control but don't own"); + private static final FilterCreaturePermanent filterNonlegendary = new FilterCreaturePermanent("nonlegendary creature"); static { filter.add(TargetController.NOT_YOU.getOwnerPredicate()); + filterNonlegendary.add(Predicates.not(SuperType.LEGENDARY.getPredicate())); } private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter, 1); @@ -56,7 +57,7 @@ public final class TheHorusHeresy extends CardImpl { // I -- For each opponent, gain control of up to one target nonlegendary creature that player controls for as long as The Horus Heresy remains on the battlefield. sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_I, ability -> { ability.addEffect(new TheHorusHeresyControlEffect()); - ability.setTargetAdjuster(TheHorusHeresyAdjuster.instance); + ability.setTargetAdjuster(new EachOpponentPermanentTargetsAdjuster(new TargetPermanent(0, 1, filterNonlegendary))); }); // II -- Draw a card for each creature you control but don't own. @@ -77,25 +78,6 @@ public final class TheHorusHeresy extends CardImpl { } } -enum TheHorusHeresyAdjuster implements TargetAdjuster { - instance; - - @Override - public void adjustTargets(Ability ability, Game game) { - ability.getTargets().clear(); - for (UUID opponentId : game.getOpponents(ability.getControllerId())) { - Player player = game.getPlayer(opponentId); - if (player == null) { - continue; - } - FilterPermanent filter = new FilterCreaturePermanent("nonlegendary creature controlled by " + player.getName()); - filter.add(Predicates.not(SuperType.LEGENDARY.getPredicate())); - filter.add(new ControllerIdPredicate(opponentId)); - ability.addTarget(new TargetPermanent(0, 1, filter)); - } - } -} - class TheHorusHeresyControlEffect extends GainControlTargetEffect { TheHorusHeresyControlEffect() { diff --git a/Mage.Sets/src/mage/cards/t/TheKeyToTheVault.java b/Mage.Sets/src/mage/cards/t/TheKeyToTheVault.java new file mode 100644 index 00000000000..f1a11030cd2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheKeyToTheVault.java @@ -0,0 +1,91 @@ +package mage.cards.t; + +import mage.abilities.Ability; +import mage.abilities.common.DealsDamageToAPlayerAttachedTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.EquipAbility; +import mage.cards.*; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInLibrary; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TheKeyToTheVault extends CardImpl { + + public TheKeyToTheVault(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.EQUIPMENT); + + // Whenever equipped creature deals combat damage to a player, look at that many cards from the top of your library. You may exile a nonland card from among them. Put the rest on the bottom of your library in a random order. You may cast the exiled card without paying its mana cost. + this.addAbility(new DealsDamageToAPlayerAttachedTriggeredAbility( + new TheKeyToTheVaultEffect(), "equipped", + false, false, true + )); + + // Equip {2}{U} + this.addAbility(new EquipAbility(Outcome.AddAbility, new ManaCostsImpl<>("{2}{U}"), false)); + } + + private TheKeyToTheVault(final TheKeyToTheVault card) { + super(card); + } + + @Override + public TheKeyToTheVault copy() { + return new TheKeyToTheVault(this); + } +} + +class TheKeyToTheVaultEffect extends OneShotEffect { + + TheKeyToTheVaultEffect() { + super(Outcome.Benefit); + staticText = "look at that many cards from the top of your library. " + + "You may exile a nonland card from among them. " + + "Put the rest on the bottom of your library in a random order. " + + "You may cast the exiled card without paying its mana cost"; + } + + private TheKeyToTheVaultEffect(final TheKeyToTheVaultEffect effect) { + super(effect); + } + + @Override + public TheKeyToTheVaultEffect copy() { + return new TheKeyToTheVaultEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + int damage = (Integer) getValue("damage"); + Cards cards = new CardsImpl(player.getLibrary().getTopCards(game, damage)); + if (cards.isEmpty()) { + return false; + } + TargetCard target = new TargetCardInLibrary(0, 1, StaticFilters.FILTER_CARD_NON_LAND); + player.choose(outcome, cards, target, source, game); + Card card = game.getCard(target.getFirstTarget()); + if (card != null) { + player.moveCards(card, Zone.EXILED, source, game); + } + cards.retainZone(Zone.LIBRARY, game); + player.putCardsOnBottomOfLibrary(cards, game, source, false); + CardUtil.castSpellWithAttributesForFree(player, source, game, card); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TheMeep.java b/Mage.Sets/src/mage/cards/t/TheMeep.java index 978dd16c1ad..7b7fc024ec0 100644 --- a/Mage.Sets/src/mage/cards/t/TheMeep.java +++ b/Mage.Sets/src/mage/cards/t/TheMeep.java @@ -33,7 +33,7 @@ public final class TheMeep extends CardImpl { this.toughness = new MageInt(4); // Ward--Pay 3 life. - this.addAbility(new WardAbility(new PayLifeCost(3))); + this.addAbility(new WardAbility(new PayLifeCost(3), false)); // Whenever The Meep attacks, you may sacrifice another creature. If you do, creatures you control have base power and toughness X/X until end of turn, where X is the sacrificed creature's mana value. this.addAbility(new AttacksTriggeredAbility(new TheMeepEffect())); diff --git a/Mage.Sets/src/mage/cards/t/TheRavensWarning.java b/Mage.Sets/src/mage/cards/t/TheRavensWarning.java index 2b4736baa1f..658baa15a57 100644 --- a/Mage.Sets/src/mage/cards/t/TheRavensWarning.java +++ b/Mage.Sets/src/mage/cards/t/TheRavensWarning.java @@ -1,7 +1,5 @@ package mage.cards.t; -import java.util.HashSet; -import java.util.Set; import java.util.UUID; import mage.abilities.DelayedTriggeredAbility; @@ -16,11 +14,10 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.game.Game; -import mage.game.events.DamagedPlayerEvent; +import mage.game.events.DamagedBatchForOnePlayerEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.permanent.token.BlueBirdToken; -import mage.target.targetpointer.FixedTarget; /** * @@ -67,8 +64,6 @@ public final class TheRavensWarning extends CardImpl { class TheRavensWarningTriggeredAbility extends DelayedTriggeredAbility { - private final Set damagedPlayerIds = new HashSet<>(); - public TheRavensWarningTriggeredAbility() { super(new LookAtTargetPlayerHandEffect(), Duration.EndOfTurn, false); this.addEffect(new DrawCardSourceControllerEffect(1)); @@ -86,28 +81,27 @@ class TheRavensWarningTriggeredAbility extends DelayedTriggeredAbility { // Code based on ControlledCreaturesDealCombatDamagePlayerTriggeredAbility @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PLAYER - || event.getType() == GameEvent.EventType.COMBAT_DAMAGE_STEP_PRIORITY - || event.getType() == GameEvent.EventType.ZONE_CHANGE; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PLAYER; } @Override public boolean checkTrigger(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.DAMAGED_PLAYER) { - DamagedPlayerEvent damageEvent = (DamagedPlayerEvent) event; - Permanent p = game.getPermanent(event.getSourceId()); - if (damageEvent.isCombatDamage() && p != null && p.isControlledBy(this.getControllerId()) - && !damagedPlayerIds.contains(event.getPlayerId()) && p.hasAbility(FlyingAbility.getInstance(), game)) { - damagedPlayerIds.add(event.getPlayerId()); - this.getEffects().get(0).setTargetPointer(new FixedTarget(event.getPlayerId())); - return true; - } - } - if (event.getType() == GameEvent.EventType.COMBAT_DAMAGE_STEP_PRIORITY || - (event.getType() == GameEvent.EventType.ZONE_CHANGE && event.getTargetId().equals(getSourceId()))) { - damagedPlayerIds.clear(); - } - return false; + + DamagedBatchForOnePlayerEvent dEvent = (DamagedBatchForOnePlayerEvent) event; + + int flyingDamage = dEvent.getEvents() + .stream() + .filter(ev -> { + if (!ev.getSourceId().equals(controllerId)) { + return false; + } + Permanent permanent = game.getPermanentOrLKIBattlefield(ev.getSourceId()); + return permanent != null && permanent.isCreature() && permanent.hasAbility(FlyingAbility.getInstance(), game); + }) + .mapToInt(GameEvent::getAmount) + .sum(); + + return flyingDamage > 0 && dEvent.isCombatDamage(); } @Override diff --git a/Mage.Sets/src/mage/cards/t/TheRealityChip.java b/Mage.Sets/src/mage/cards/t/TheRealityChip.java index 21d03266524..360e3213c3a 100644 --- a/Mage.Sets/src/mage/cards/t/TheRealityChip.java +++ b/Mage.Sets/src/mage/cards/t/TheRealityChip.java @@ -6,7 +6,7 @@ import mage.abilities.condition.Condition; import mage.abilities.condition.common.AttachedToMatchesFilterCondition; import mage.abilities.decorator.ConditionalAsThoughEffect; import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; -import mage.abilities.effects.common.continuous.PlayTheTopCardEffect; +import mage.abilities.effects.common.continuous.PlayFromTopOfLibraryEffect; import mage.abilities.keyword.ReconfigureAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -37,7 +37,7 @@ public final class TheRealityChip extends CardImpl { this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); // As long as The Reality Chip is attached to a creature, you may play lands and cast spells from the top of your library. - this.addAbility(new SimpleStaticAbility(new ConditionalAsThoughEffect(new PlayTheTopCardEffect(), condition) + this.addAbility(new SimpleStaticAbility(new ConditionalAsThoughEffect(new PlayFromTopOfLibraryEffect(), condition) .setText("as long as {this} is attached to a creature, you may play lands and cast spells from the top of your library"))); // Reconfigure {2}{U} diff --git a/Mage.Sets/src/mage/cards/t/TheSpaceFamilyGoblinson.java b/Mage.Sets/src/mage/cards/t/TheSpaceFamilyGoblinson.java index 4a81378e370..1ae43186fb3 100644 --- a/Mage.Sets/src/mage/cards/t/TheSpaceFamilyGoblinson.java +++ b/Mage.Sets/src/mage/cards/t/TheSpaceFamilyGoblinson.java @@ -104,7 +104,7 @@ class TheSpaceFamilyGoblinsonWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { if (event.getType() == GameEvent.EventType.DIE_ROLLED) { - map.compute(event.getPlayerId(), CardUtil::setOrIncrementValue); + map.compute(event.getTargetId(), CardUtil::setOrIncrementValue); } } @@ -146,7 +146,7 @@ class TheSpaceFamilyGoblinsonTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { - return isControlledBy(event.getPlayerId()); + return isControlledBy(event.getTargetId()); } } diff --git a/Mage.Sets/src/mage/cards/t/TheTrueScriptures.java b/Mage.Sets/src/mage/cards/t/TheTrueScriptures.java index eee0ca95a36..ba9d9d4d127 100644 --- a/Mage.Sets/src/mage/cards/t/TheTrueScriptures.java +++ b/Mage.Sets/src/mage/cards/t/TheTrueScriptures.java @@ -13,14 +13,11 @@ 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.FilterCreatureOrPlaneswalkerPermanent; -import mage.filter.predicate.permanent.ControllerIdPredicate; import mage.game.Game; import mage.players.Player; -import mage.target.TargetPermanent; -import mage.target.targetadjustment.TargetAdjuster; +import mage.target.common.TargetCreatureOrPlaneswalker; +import mage.target.targetadjustment.EachOpponentPermanentTargetsAdjuster; import mage.target.targetpointer.EachTargetPointer; import java.util.Collection; @@ -49,7 +46,7 @@ public final class TheTrueScriptures extends CardImpl { ability -> { ability.addEffect(new DestroyTargetEffect().setTargetPointer(new EachTargetPointer()) .setText("for each opponent, destroy up to one target creature or planeswalker that player controls")); - ability.setTargetAdjuster(TheTrueScripturesAdjuster.instance); + ability.setTargetAdjuster(new EachOpponentPermanentTargetsAdjuster(new TargetCreatureOrPlaneswalker(0,1))); } ); @@ -79,29 +76,6 @@ public final class TheTrueScriptures extends CardImpl { } } -enum TheTrueScripturesAdjuster implements TargetAdjuster { - instance; - - @Override - public void adjustTargets(Ability ability, Game game) { - ability.getTargets().clear(); - for (UUID playerId : game.getOpponents(ability.getControllerId())) { - Player player = game.getPlayer(playerId); - if (player == null) { - continue; - } - FilterPermanent filter = new FilterCreatureOrPlaneswalkerPermanent( - "creature or planeswalker controlled by " + player.getName() - ); - filter.add(new ControllerIdPredicate(playerId)); - if (game.getBattlefield().count(filter, ability.getControllerId(), ability, game) == 0) { - continue; - } - ability.addTarget(new TargetPermanent(0, 1, filter)); - } - } -} - class TheTrueScripturesEffect extends OneShotEffect { TheTrueScripturesEffect() { diff --git a/Mage.Sets/src/mage/cards/t/ThisTownAintBigEnough.java b/Mage.Sets/src/mage/cards/t/ThisTownAintBigEnough.java new file mode 100644 index 00000000000..9d43681f1ed --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/ThisTownAintBigEnough.java @@ -0,0 +1,45 @@ +package mage.cards.t; + +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.SourceTargetsPermanentCondition; +import mage.abilities.effects.common.ReturnToHandTargetEffect; +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.StaticFilters; +import mage.target.common.TargetNonlandPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ThisTownAintBigEnough extends CardImpl { + + private static final Condition condition = new SourceTargetsPermanentCondition(StaticFilters.FILTER_CONTROLLED_A_PERMANENT); + + public ThisTownAintBigEnough(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{4}{U}"); + + // This spell costs {3} less to cast if it targets a permanent you control. + this.addAbility(new SimpleStaticAbility( + Zone.ALL, new SpellCostReductionSourceEffect(3, condition).setCanWorksOnStackOnly(true) + ).setRuleAtTheTop(true)); + + // Return up to two target nonland permanents to their owners' hands. + this.getSpellAbility().addEffect(new ReturnToHandTargetEffect()); + this.getSpellAbility().addTarget(new TargetNonlandPermanent(0, 2)); + } + + private ThisTownAintBigEnough(final ThisTownAintBigEnough card) { + super(card); + } + + @Override + public ThisTownAintBigEnough copy() { + return new ThisTownAintBigEnough(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/ThreeStepsAhead.java b/Mage.Sets/src/mage/cards/t/ThreeStepsAhead.java new file mode 100644 index 00000000000..fb09123cee1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/ThreeStepsAhead.java @@ -0,0 +1,53 @@ +package mage.cards.t; + +import mage.abilities.Mode; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.CounterTargetEffect; +import mage.abilities.effects.common.CreateTokenCopyTargetEffect; +import mage.abilities.effects.common.DrawDiscardControllerEffect; +import mage.abilities.keyword.SpreeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; +import mage.target.TargetPermanent; +import mage.target.TargetSpell; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ThreeStepsAhead extends CardImpl { + + public ThreeStepsAhead(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{U}"); + + // Spree + this.addAbility(new SpreeAbility(this)); + + // + {1}{U} -- Counter target spell. + this.getSpellAbility().addEffect(new CounterTargetEffect()); + this.getSpellAbility().addTarget(new TargetSpell()); + this.getSpellAbility().withFirstModeCost(new ManaCostsImpl<>("{1}{U}")); + + // + {3} -- Create a token that's a copy of target artifact or creature you control. + this.getSpellAbility().addMode(new Mode(new CreateTokenCopyTargetEffect()) + .addTarget(new TargetPermanent(StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACT_OR_CREATURE)) + .withCost(new GenericManaCost(3))); + + // + {2} -- Draw two cards, then discard a card. + this.getSpellAbility().addMode(new Mode(new DrawDiscardControllerEffect(2, 1)) + .withCost(new GenericManaCost(2))); + } + + private ThreeStepsAhead(final ThreeStepsAhead card) { + super(card); + } + + @Override + public ThreeStepsAhead copy() { + return new ThreeStepsAhead(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/ThrowFromTheSaddle.java b/Mage.Sets/src/mage/cards/t/ThrowFromTheSaddle.java new file mode 100644 index 00000000000..e010cdcca59 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/ThrowFromTheSaddle.java @@ -0,0 +1,82 @@ +package mage.cards.t; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DamageWithPowerFromOneToAnotherTargetEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetCreaturePermanent; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class ThrowFromTheSaddle extends CardImpl { + + public ThrowFromTheSaddle(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{G}"); + + // Target creature you control gets +1/+1 until end of turn. Put a +1/+1 counter on it instead if it's a Mount. Then it deals damage equal to its power to target creature you don't control. + this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent()); + this.getSpellAbility().addTarget(new TargetCreaturePermanent(StaticFilters.FILTER_CREATURE_YOU_DONT_CONTROL)); + this.getSpellAbility().addEffect(new ThrowFromTheSaddleEffect()); + this.getSpellAbility().addEffect(new DamageWithPowerFromOneToAnotherTargetEffect("it").concatBy("Then")); + } + + private ThrowFromTheSaddle(final ThrowFromTheSaddle card) { + super(card); + } + + @Override + public ThrowFromTheSaddle copy() { + return new ThrowFromTheSaddle(this); + } +} + +class ThrowFromTheSaddleEffect extends OneShotEffect { + + ThrowFromTheSaddleEffect() { + super(Outcome.BoostCreature); + staticText = "Target creature you control gets +1/+1 until end of turn. " + + "Put a +1/+1 counter on it instead if it's a Mount."; + } + + private ThrowFromTheSaddleEffect(final ThrowFromTheSaddleEffect effect) { + super(effect); + } + + @Override + public ThrowFromTheSaddleEffect copy() { + return new ThrowFromTheSaddleEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(source.getFirstTarget()); + if (permanent == null) { + return false; + } + if (SubType.MOUNT.getPredicate().apply(permanent, game)) { + permanent.addCounters(CounterType.P1P1.createInstance(), source, game); + } else { + game.addEffect( + new BoostTargetEffect(1, 1) + .setTargetPointer(new FixedTarget(permanent.getId())), + source + ); + } + return true; + } + +} diff --git a/Mage.Sets/src/mage/cards/t/ThunderLasso.java b/Mage.Sets/src/mage/cards/t/ThunderLasso.java new file mode 100644 index 00000000000..cc71b723902 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/ThunderLasso.java @@ -0,0 +1,63 @@ +package mage.cards.t; + +import mage.abilities.Ability; +import mage.abilities.common.AttacksAttachedTriggeredAbility; +import mage.abilities.common.EntersBattlefieldAttachToTarget; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.TapTargetEffect; +import mage.abilities.effects.common.continuous.BoostEquippedEffect; +import mage.abilities.keyword.EquipAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AttachmentType; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.DefendingPlayerControlsAttachedAttackingPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ThunderLasso extends CardImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent("creature defending player controls"); + + static { + filter.add(DefendingPlayerControlsAttachedAttackingPredicate.instance); + } + + public ThunderLasso(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{W}"); + + this.subtype.add(SubType.EQUIPMENT); + + // When Thunder Lasso enters the battlefield, attach it to target creature you control. + this.addAbility(new EntersBattlefieldAttachToTarget()); + + // Equipped creature gets +1/+1. + this.addAbility(new SimpleStaticAbility(new BoostEquippedEffect(1, 1))); + + // Whenever equipped creature attacks, tap target creature defending player controls. + Ability ability = new AttacksAttachedTriggeredAbility( + new TapTargetEffect(), AttachmentType.EQUIPMENT, false + ); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + + // Equip {2} + this.addAbility(new EquipAbility(2, false)); + } + + private ThunderLasso(final ThunderLasso card) { + super(card); + } + + @Override + public ThunderLasso copy() { + return new ThunderLasso(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/ThunderSalvo.java b/Mage.Sets/src/mage/cards/t/ThunderSalvo.java new file mode 100644 index 00000000000..c1bd2856545 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/ThunderSalvo.java @@ -0,0 +1,75 @@ +package mage.cards.t; + +import mage.abilities.Ability; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.IntPlusDynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.hint.ValueHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.game.Game; +import mage.game.stack.Spell; +import mage.target.common.TargetCreaturePermanent; +import mage.watchers.common.SpellsCastWatcher; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class ThunderSalvo extends CardImpl { + + public ThunderSalvo(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{R}"); + + // Thunder Salvo deals X damage to target creature, where X is 2 plus the number of other spells you've cast this turn. + this.getSpellAbility().addEffect(new DamageTargetEffect(new IntPlusDynamicValue(2, ThunderSalvoValue.instance)) + .setText("{this} deals X damage to target creature, where X is 2 plus the number of other spells you've cast this turn.")); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + this.getSpellAbility().addHint(new ValueHint("Number of other spells you've cast this turn", ThunderSalvoValue.instance)); + } + + private ThunderSalvo(final ThunderSalvo card) { + super(card); + } + + @Override + public ThunderSalvo copy() { + return new ThunderSalvo(this); + } +} + +enum ThunderSalvoValue implements DynamicValue { + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + SpellsCastWatcher watcher = game.getState().getWatcher(SpellsCastWatcher.class); + if (watcher == null) { + return 0; + } + Spell spell = game.getSpell(sourceAbility.getSourceId()); + return watcher.getSpellsCastThisTurn(sourceAbility.getControllerId()) + .stream() + .filter(s -> spell == null || !spell.getId().equals(s.getId())) + .mapToInt(k -> 1) + .sum(); + } + + @Override + public ThunderSalvoValue copy() { + return instance; + } + + @Override + public String getMessage() { + return "Number of other spells you've cast this turn"; + } + + @Override + public String toString() { + return "1"; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/t/TinybonesJoinsUp.java b/Mage.Sets/src/mage/cards/t/TinybonesJoinsUp.java new file mode 100644 index 00000000000..3d9ce8c04b7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TinybonesJoinsUp.java @@ -0,0 +1,58 @@ +package mage.cards.t; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.LoseLifeTargetEffect; +import mage.abilities.effects.common.MillCardsTargetEffect; +import mage.abilities.effects.common.discard.DiscardTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SuperType; +import mage.filter.common.FilterCreaturePermanent; +import mage.target.TargetPlayer; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class TinybonesJoinsUp extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("legendary creature"); + + static { + filter.add(SuperType.LEGENDARY.getPredicate()); + } + + public TinybonesJoinsUp(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{B}"); + + this.supertype.add(SuperType.LEGENDARY); + + // When Tinybones Joins Up enters the battlefield, any number of target players each discard a card. + Ability ability = new EntersBattlefieldTriggeredAbility(new DiscardTargetEffect(1) + .setText("any number of target players each discard a card")); + ability.addTarget(new TargetPlayer(0, Integer.MAX_VALUE, false)); + this.addAbility(ability); + + // Whenever a legendary creature enters the battlefield under your control, any number of target players each mill a card and lose 1 life. + ability = new EntersBattlefieldControlledTriggeredAbility( + new MillCardsTargetEffect(1).setText("any number of target players each mill a card"), + filter + ); + ability.addEffect(new LoseLifeTargetEffect(1).setText("and lose 1 life")); + ability.addTarget(new TargetPlayer(0, Integer.MAX_VALUE, false)); + this.addAbility(ability); + } + + private TinybonesJoinsUp(final TinybonesJoinsUp card) { + super(card); + } + + @Override + public TinybonesJoinsUp copy() { + return new TinybonesJoinsUp(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TinybonesThePickpocket.java b/Mage.Sets/src/mage/cards/t/TinybonesThePickpocket.java new file mode 100644 index 00000000000..735802e6a5c --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TinybonesThePickpocket.java @@ -0,0 +1,101 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.common.MayCastTargetCardEffect; +import mage.abilities.keyword.DeathtouchAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.common.FilterPermanentCard; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.card.OwnerIdPredicate; +import mage.game.Game; +import mage.game.events.DamagedPlayerEvent; +import mage.game.events.GameEvent; +import mage.players.Player; +import mage.target.Target; +import mage.target.common.TargetCardInGraveyard; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class TinybonesThePickpocket extends CardImpl { + + public TinybonesThePickpocket(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SKELETON); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Deathtouch + this.addAbility(DeathtouchAbility.getInstance()); + + // Whenever Tinybones, the Pickpocket deals combat damage to a player, you may cast target nonland permanent card from that player's graveyard, and mana of any type can be spent to cast that spell. + this.addAbility(new TinybonesThePickpocketTriggeredAbility()); + } + + private TinybonesThePickpocket(final TinybonesThePickpocket card) { + super(card); + } + + @Override + public TinybonesThePickpocket copy() { + return new TinybonesThePickpocket(this); + } +} + +/** + * Similar to {@link mage.cards.w.WrexialTheRisenDeep} + */ +class TinybonesThePickpocketTriggeredAbility extends TriggeredAbilityImpl { + + TinybonesThePickpocketTriggeredAbility() { + super( + Zone.BATTLEFIELD, + new MayCastTargetCardEffect(CastManaAdjustment.AS_THOUGH_ANY_MANA_TYPE, false) + .setText("you may cast target nonland permanent card from " + + "that player's graveyard, and mana of any type can be spent to cast that spell"), + false + ); + setTriggerPhrase("Whenever {this} deals combat damage to a player, "); + } + + private TinybonesThePickpocketTriggeredAbility(final TinybonesThePickpocketTriggeredAbility ability) { + super(ability); + } + + @Override + public TinybonesThePickpocketTriggeredAbility copy() { + return new TinybonesThePickpocketTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DAMAGED_PLAYER; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (!event.getSourceId().equals(this.sourceId) || !((DamagedPlayerEvent) event).isCombatDamage()) { + return false; + } + Player damagedPlayer = game.getPlayer(event.getTargetId()); + if (damagedPlayer == null) { + return false; + } + FilterCard filter = new FilterPermanentCard("nonland permanent card from " + damagedPlayer.getName() + "'s graveyard"); + filter.add(new OwnerIdPredicate(damagedPlayer.getId())); + filter.add(Predicates.not(CardType.LAND.getPredicate())); + Target target = new TargetCardInGraveyard(filter); + this.getTargets().clear(); + this.addTarget(target); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TolarianContempt.java b/Mage.Sets/src/mage/cards/t/TolarianContempt.java index 90f825ad1fe..b54ced1aa85 100644 --- a/Mage.Sets/src/mage/cards/t/TolarianContempt.java +++ b/Mage.Sets/src/mage/cards/t/TolarianContempt.java @@ -14,12 +14,11 @@ import mage.counters.CounterType; import mage.filter.FilterPermanent; import mage.filter.common.FilterCreaturePermanent; import mage.filter.common.FilterOpponentsCreaturePermanent; -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.targetadjustment.TargetAdjuster; +import mage.target.targetadjustment.EachOpponentPermanentTargetsAdjuster; import mage.target.targetpointer.EachTargetPointer; import java.util.UUID; @@ -31,6 +30,12 @@ public final class TolarianContempt extends CardImpl { private static final FilterPermanent filter = new FilterOpponentsCreaturePermanent("creature your opponents control"); + private static final FilterPermanent filterRejection + = new FilterCreaturePermanent("creature with a rejection counter on it"); + + static { + filter.add(CounterType.REJECTION.getPredicate()); + } public TolarianContempt(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{U}{U}"); @@ -43,7 +48,7 @@ public final class TolarianContempt extends CardImpl { // At the beginning of your end step, for each opponent, choose up to one target creature they control with a rejection counter on it. That creature's owner puts it on the top or bottom of their library. this.addAbility(new BeginningOfEndStepTriggeredAbility( new TolarianContemptEffect(), TargetController.YOU, false - ).setTargetAdjuster(TolarianContemptAdjuster.instance)); + ).setTargetAdjuster(new EachOpponentPermanentTargetsAdjuster(new TargetPermanent(0,1, filterRejection)))); } private TolarianContempt(final TolarianContempt card) { @@ -56,27 +61,6 @@ public final class TolarianContempt extends CardImpl { } } -enum TolarianContemptAdjuster implements TargetAdjuster { - instance; - - @Override - public void adjustTargets(Ability ability, Game game) { - ability.getTargets().clear(); - for (UUID opponentId : game.getOpponents(ability.getControllerId())) { - Player opponent = game.getPlayer(opponentId); - if (opponent == null) { - continue; - } - FilterPermanent filter = new FilterCreaturePermanent( - "creature controlled by " + opponent.getName() + " with a rejection counter on it" - ); - filter.add(CounterType.REJECTION.getPredicate()); - filter.add(new ControllerIdPredicate(opponentId)); - ability.addTarget(new TargetPermanent(0, 1, filter)); - } - } -} - class TolarianContemptEffect extends OneShotEffect { TolarianContemptEffect() { diff --git a/Mage.Sets/src/mage/cards/t/TombTrawler.java b/Mage.Sets/src/mage/cards/t/TombTrawler.java new file mode 100644 index 00000000000..290316257fe --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TombTrawler.java @@ -0,0 +1,42 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.PutOnLibraryTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TombTrawler extends CardImpl { + + public TombTrawler(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{2}"); + + this.subtype.add(SubType.GOLEM); + this.power = new MageInt(0); + this.toughness = new MageInt(4); + + // {2}: Put target card from your graveyard on the bottom of your library. + Ability ability = new SimpleActivatedAbility(new PutOnLibraryTargetEffect(false), new GenericManaCost(2)); + ability.addTarget(new TargetCardInYourGraveyard()); + this.addAbility(ability); + } + + private TombTrawler(final TombTrawler card) { + super(card); + } + + @Override + public TombTrawler copy() { + return new TombTrawler(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/ToralfGodOfFury.java b/Mage.Sets/src/mage/cards/t/ToralfGodOfFury.java index 8ffc1f9bb88..2af8737fb86 100644 --- a/Mage.Sets/src/mage/cards/t/ToralfGodOfFury.java +++ b/Mage.Sets/src/mage/cards/t/ToralfGodOfFury.java @@ -26,6 +26,7 @@ import mage.filter.common.FilterPermanentOrPlayer; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.MageObjectReferencePredicate; import mage.game.Game; +import mage.game.events.DamagedBatchForOnePermanentEvent; import mage.game.events.DamagedEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; @@ -103,24 +104,28 @@ class ToralfGodOfFuryTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT; } @Override public boolean checkTrigger(GameEvent event, Game game) { - DamagedEvent dEvent = (DamagedEvent) event; - if (dEvent.getExcess() < 1 + DamagedBatchForOnePermanentEvent dEvent = (DamagedBatchForOnePermanentEvent) event; + int excessDamage = dEvent.getEvents() + .stream() + .mapToInt(DamagedEvent::getExcess) + .sum(); + + if (excessDamage < 1 || dEvent.isCombatDamage() || !game.getOpponents(getControllerId()).contains(game.getControllerId(event.getTargetId()))) { return false; } this.getEffects().clear(); this.getTargets().clear(); - int excessDamage = dEvent.getExcess(); this.addEffect(new DamageTargetEffect(excessDamage)); FilterPermanentOrPlayer filter = new FilterAnyTarget(); filter.getPermanentFilter().add(Predicates.not(new MageObjectReferencePredicate(event.getTargetId(), game))); - this.addTarget(new TargetPermanentOrPlayer(filter).withChooseHint(Integer.toString(excessDamage) + " damage")); + this.addTarget(new TargetPermanentOrPlayer(filter).withChooseHint(excessDamage + " damage")); return true; } diff --git a/Mage.Sets/src/mage/cards/t/TorrentialGearhulk.java b/Mage.Sets/src/mage/cards/t/TorrentialGearhulk.java index 6f8ace7ed88..4c9efb10884 100644 --- a/Mage.Sets/src/mage/cards/t/TorrentialGearhulk.java +++ b/Mage.Sets/src/mage/cards/t/TorrentialGearhulk.java @@ -3,11 +3,12 @@ package mage.cards.t; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.effects.common.MayCastTargetThenExileEffect; +import mage.abilities.effects.common.MayCastTargetCardEffect; import mage.abilities.keyword.FlashAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.CastManaAdjustment; import mage.constants.SubType; import mage.filter.FilterCard; import mage.target.common.TargetCardInYourGraveyard; @@ -37,7 +38,7 @@ public final class TorrentialGearhulk extends CardImpl { // When Torrential Gearhulk enters the battlefield, you may cast target // instant card from your graveyard without paying its mana cost. // If that card would be put into your graveyard this turn, exile it instead. - Ability ability = new EntersBattlefieldTriggeredAbility(new MayCastTargetThenExileEffect(true)); + Ability ability = new EntersBattlefieldTriggeredAbility(new MayCastTargetCardEffect(CastManaAdjustment.WITHOUT_PAYING_MANA_COST, true)); ability.addTarget(new TargetCardInYourGraveyard(filter)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/t/ToshiroUmezawa.java b/Mage.Sets/src/mage/cards/t/ToshiroUmezawa.java index 6b36c491924..feeca4e9760 100644 --- a/Mage.Sets/src/mage/cards/t/ToshiroUmezawa.java +++ b/Mage.Sets/src/mage/cards/t/ToshiroUmezawa.java @@ -3,7 +3,7 @@ package mage.cards.t; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.DiesCreatureTriggeredAbility; -import mage.abilities.effects.common.MayCastTargetThenExileEffect; +import mage.abilities.effects.common.MayCastTargetCardEffect; import mage.abilities.effects.common.replacement.ThatSpellGraveyardExileReplacementEffect; import mage.abilities.keyword.BushidoAbility; import mage.cards.CardImpl; @@ -39,7 +39,7 @@ public final class ToshiroUmezawa extends CardImpl { // Bushido 1 this.addAbility(new BushidoAbility(1)); // Whenever a creature an opponent controls dies, you may cast target instant card from your graveyard. If that card would be put into a graveyard this turn, exile it instead. - Ability ability = new DiesCreatureTriggeredAbility(new MayCastTargetThenExileEffect(false) + Ability ability = new DiesCreatureTriggeredAbility(new MayCastTargetCardEffect(true) .setText("you may cast target instant card from your graveyard. " + ThatSpellGraveyardExileReplacementEffect.RULE_A), true, StaticFilters.FILTER_OPPONENTS_PERMANENT_A_CREATURE); diff --git a/Mage.Sets/src/mage/cards/t/TrainedArynx.java b/Mage.Sets/src/mage/cards/t/TrainedArynx.java new file mode 100644 index 00000000000..8b7882ca5cc --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TrainedArynx.java @@ -0,0 +1,51 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksWhileSaddledTriggeredAbility; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.effects.keyword.ScryEffect; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.abilities.keyword.SaddleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TrainedArynx extends CardImpl { + + public TrainedArynx(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.subtype.add(SubType.CAT); + this.subtype.add(SubType.BEAST); + this.subtype.add(SubType.MOUNT); + this.power = new MageInt(3); + this.toughness = new MageInt(1); + + // Whenever Trained Arynx attacks while saddled, it gains first strike until end of turn. Scry 1. + Ability ability = new AttacksWhileSaddledTriggeredAbility(new GainAbilitySourceEffect( + FirstStrikeAbility.getInstance(), Duration.EndOfTurn + ).setText("it gains first strike until end of turn")); + ability.addEffect(new ScryEffect(1)); + this.addAbility(ability); + + // Saddle 2 + this.addAbility(new SaddleAbility(2)); + } + + private TrainedArynx(final TrainedArynx card) { + super(card); + } + + @Override + public TrainedArynx copy() { + return new TrainedArynx(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TransmutationFont.java b/Mage.Sets/src/mage/cards/t/TransmutationFont.java new file mode 100644 index 00000000000..31abfa2d552 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TransmutationFont.java @@ -0,0 +1,139 @@ +package mage.cards.t; + +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.common.SimpleActivatedAbility; +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.search.SearchLibraryPutInPlayEffect; +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.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterArtifactPermanent; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.BloodToken; +import mage.game.permanent.token.ClueArtifactToken; +import mage.game.permanent.token.FoodToken; +import mage.game.permanent.token.Token; +import mage.players.Player; +import mage.target.common.TargetCardInLibrary; +import mage.target.common.TargetSacrifice; +import mage.util.CardUtil; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.function.Supplier; + +/** + * @author TheElk801 + */ +public final class TransmutationFont extends CardImpl { + + public TransmutationFont(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{5}"); + + // {T}: Create your choice of a Blood token, a Clue token, or a Food token. + this.addAbility(new SimpleActivatedAbility(new TransmutationFontEffect(), new TapSourceCost())); + + // {3}, {T}, Sacrifice three artifact tokens with different names: Search your library for an artifact card, put it onto the battlefield, then shuffle. Activate only as a sorcery. + Ability ability = new ActivateAsSorceryActivatedAbility(new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(StaticFilters.FILTER_CARD_ARTIFACT)), new GenericManaCost(3)); + ability.addCost(new TapSourceCost()); + ability.addCost(new SacrificeTargetCost(new TransmutationFontTarget())); + this.addAbility(ability); + } + + private TransmutationFont(final TransmutationFont card) { + super(card); + } + + @Override + public TransmutationFont copy() { + return new TransmutationFont(this); + } +} + +class TransmutationFontEffect extends OneShotEffect { + + private static final Map> map = new HashMap<>(); + + static { + map.put("Blood", BloodToken::new); + map.put("Clue", ClueArtifactToken::new); + map.put("Food", FoodToken::new); + } + + TransmutationFontEffect() { + super(Outcome.Benefit); + staticText = "create your choice of a Blood token, a Clue token, or a Food token"; + } + + private TransmutationFontEffect(final TransmutationFontEffect effect) { + super(effect); + } + + @Override + public TransmutationFontEffect copy() { + return new TransmutationFontEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Choice choice = new ChoiceImpl(true); + choice.setMessage("Choose a token to create"); + choice.setChoices(map.keySet()); + player.choose(outcome, choice, game); + return map.get(choice.getChoice()).get().putOntoBattlefield(1, game, source); + } +} + +class TransmutationFontTarget extends TargetSacrifice { + + private static final FilterPermanent filter + = new FilterArtifactPermanent("artifact tokens with different names"); + + static { + filter.add(TokenPredicate.TRUE); + } + + TransmutationFontTarget() { + super(3, 3, filter); + } + + private TransmutationFontTarget(final TransmutationFontTarget target) { + super(target); + } + + @Override + public TransmutationFontTarget copy() { + return new TransmutationFontTarget(this); + } + + @Override + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (!super.canTarget(playerId, id, source, game)) { + return false; + } + Permanent permanent = game.getPermanent(id); + return this + .getTargets() + .stream() + .map(game::getPermanent) + .filter(Objects::nonNull) + .noneMatch(p -> CardUtil.haveSameNames(p, permanent)); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TrashTheTown.java b/Mage.Sets/src/mage/cards/t/TrashTheTown.java new file mode 100644 index 00000000000..a1c8fe7d849 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TrashTheTown.java @@ -0,0 +1,58 @@ +package mage.cards.t; + +import mage.abilities.Mode; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.keyword.SpreeAbility; +import mage.abilities.keyword.TrampleAbility; +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 TrashTheTown extends CardImpl { + + public TrashTheTown(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{G}"); + + // Spree + this.addAbility(new SpreeAbility(this)); + + // + {2} -- Put two +1/+1 counters on target creature. + this.getSpellAbility().addEffect(new AddCountersTargetEffect(CounterType.P1P1.createInstance(2))); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + this.getSpellAbility().withFirstModeCost(new GenericManaCost(2)); + + // + {1} -- Target creature gains trample until end of turn. + this.getSpellAbility().addMode(new Mode(new GainAbilityTargetEffect(TrampleAbility.getInstance())) + .addTarget(new TargetCreaturePermanent()) + .withCost(new GenericManaCost(1))); + + // + {1} -- Until end of turn, target creature gains "Whenever this creature deals combat damage to a player, draw two cards." + this.getSpellAbility().addMode(new Mode(new GainAbilityTargetEffect( + new DealsCombatDamageToAPlayerTriggeredAbility( + new DrawCardSourceControllerEffect(2), false + ).setTriggerPhrase("Whenever this creature deals combat damage to a player, ") + ).setText("Until end of turn, target creature gains \"Whenever this creature deals combat damage to a player, draw two cards.\"")) + .addTarget(new TargetCreaturePermanent()) + .withCost(new GenericManaCost(1))); + } + + private TrashTheTown(final TrashTheTown card) { + super(card); + } + + @Override + public TrashTheTown copy() { + return new TrashTheTown(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TreasureDredger.java b/Mage.Sets/src/mage/cards/t/TreasureDredger.java new file mode 100644 index 00000000000..659d92c6e17 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TreasureDredger.java @@ -0,0 +1,46 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.PayLifeCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +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 TreasureDredger extends CardImpl { + + public TreasureDredger(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // {1}, {T}, Pay 1 life: Create a Treasure token. + Ability ability = new SimpleActivatedAbility(new CreateTokenEffect(new TreasureToken()), new GenericManaCost(1)); + ability.addCost(new TapSourceCost()); + ability.addCost(new PayLifeCost(1)); + this.addAbility(ability); + } + + private TreasureDredger(final TreasureDredger card) { + super(card); + } + + @Override + public TreasureDredger copy() { + return new TreasureDredger(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TrickShot.java b/Mage.Sets/src/mage/cards/t/TrickShot.java new file mode 100644 index 00000000000..8bf34ed7989 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TrickShot.java @@ -0,0 +1,49 @@ +package mage.cards.t; + +import mage.abilities.effects.common.DamageTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.other.AnotherTargetPredicate; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.target.common.TargetCreaturePermanent; +import mage.target.targetpointer.SecondTargetPointer; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class TrickShot extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("other target creature token"); + + static { + filter.add(new AnotherTargetPredicate(2)); + filter.add(TokenPredicate.TRUE); + } + + public TrickShot(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{4}{R}"); + + // Trick Shot deals 6 damage to target creature and 2 damage to up to one other target creature token. + this.getSpellAbility().addTarget(new TargetCreaturePermanent().setTargetTag(1)); + this.getSpellAbility().addTarget(new TargetCreaturePermanent(0, 1, filter, false).setTargetTag(2)); + this.getSpellAbility().addEffect(new DamageTargetEffect(6, true, "", true)); + this.getSpellAbility().addEffect( + new DamageTargetEffect(2, true, "", true) + .setTargetPointer(new SecondTargetPointer()) + .setText("and 2 damage to up to one other target creature token") + ); + } + + private TrickShot(final TrickShot card) { + super(card); + } + + @Override + public TrickShot copy() { + return new TrickShot(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TumbleweedRising.java b/Mage.Sets/src/mage/cards/t/TumbleweedRising.java new file mode 100644 index 00000000000..a553fc1d14b --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TumbleweedRising.java @@ -0,0 +1,66 @@ +package mage.cards.t; + +import mage.abilities.Ability; +import mage.abilities.dynamicvalue.common.GreatestPowerAmongControlledCreaturesValue; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.PlotAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.game.Game; +import mage.game.permanent.token.SeedGuardianToken; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class TumbleweedRising extends CardImpl { + + public TumbleweedRising(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{G}"); + + // Create an X/X green Elemental creature token, where X is the greatest power among creatures you control. + this.getSpellAbility().addEffect(new TumbleweedRisingEffect()); + this.getSpellAbility().addHint(GreatestPowerAmongControlledCreaturesValue.getHint()); + + // Plot {2}{G} + this.addAbility(new PlotAbility("{2}{G}")); + } + + private TumbleweedRising(final TumbleweedRising card) { + super(card); + } + + @Override + public TumbleweedRising copy() { + return new TumbleweedRising(this); + } +} + + +class TumbleweedRisingEffect extends OneShotEffect { + + TumbleweedRisingEffect() { + super(Outcome.Benefit); + staticText = "Create an X/X green Elemental creature token, " + + "where X is the greatest power among creatures you control"; + } + + private TumbleweedRisingEffect(final TumbleweedRisingEffect effect) { + super(effect); + } + + @Override + public TumbleweedRisingEffect copy() { + return new TumbleweedRisingEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + int xvalue = GreatestPowerAmongControlledCreaturesValue.instance.calculate(game, source, this); + return new CreateTokenEffect(new SeedGuardianToken(xvalue)).apply(game, source); + } +} diff --git a/Mage.Sets/src/mage/cards/u/UltramarinesHonourGuard.java b/Mage.Sets/src/mage/cards/u/UltramarinesHonourGuard.java index c701803fa12..438252d594e 100644 --- a/Mage.Sets/src/mage/cards/u/UltramarinesHonourGuard.java +++ b/Mage.Sets/src/mage/cards/u/UltramarinesHonourGuard.java @@ -2,6 +2,7 @@ package mage.cards.u; import mage.MageInt; import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.common.continuous.BoostControlledEffect; import mage.abilities.keyword.SquadAbility; import mage.cards.CardImpl; @@ -26,7 +27,7 @@ public final class UltramarinesHonourGuard extends CardImpl { this.toughness = new MageInt(2); // Squad {2} - this.addAbility(new SquadAbility()); + this.addAbility(new SquadAbility(new GenericManaCost(2))); // Other creatures you control get +1/+1. this.addAbility(new SimpleStaticAbility(new BoostControlledEffect( diff --git a/Mage.Sets/src/mage/cards/u/UnfortunateAccident.java b/Mage.Sets/src/mage/cards/u/UnfortunateAccident.java new file mode 100644 index 00000000000..dc3b4e29c36 --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/UnfortunateAccident.java @@ -0,0 +1,46 @@ +package mage.cards.u; + +import mage.abilities.Mode; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.keyword.SpreeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.game.permanent.token.MercenaryToken; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class UnfortunateAccident extends CardImpl { + + public UnfortunateAccident(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{B}"); + + // Spree + this.addAbility(new SpreeAbility(this)); + + // + {2}{B} -- Destroy target creature. + this.getSpellAbility().addEffect(new DestroyTargetEffect()); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + this.getSpellAbility().withFirstModeCost(new ManaCostsImpl<>("{2}{B}")); + + // + {1} -- Create a 1/1 red Mercenary creature token with "{T}: Target creature you control gets +1/+0 until end of turn. Activate only as a sorcery." + this.getSpellAbility().addMode(new Mode(new CreateTokenEffect(new MercenaryToken())) + .withCost(new GenericManaCost(1))); + } + + private UnfortunateAccident(final UnfortunateAccident card) { + super(card); + } + + @Override + public UnfortunateAccident copy() { + return new UnfortunateAccident(this); + } +} diff --git a/Mage.Sets/src/mage/cards/u/UnrulyKrasis.java b/Mage.Sets/src/mage/cards/u/UnrulyKrasis.java new file mode 100644 index 00000000000..b48ac0fe224 --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/UnrulyKrasis.java @@ -0,0 +1,90 @@ +package mage.cards.u; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.SetBasePowerToughnessTargetEffect; +import mage.abilities.keyword.AdaptAbility; +import mage.abilities.keyword.TrampleAbility; +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.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class UnrulyKrasis extends CardImpl { + + public UnrulyKrasis(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}{U}"); + + this.subtype.add(SubType.SHARK); + this.subtype.add(SubType.OCTOPUS); + this.subtype.add(SubType.LIZARD); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Whenever Unruly Krasis attacks, you may have the base power and toughness of another target creature you control become X/X until end of turn, where X is Unruly Krasis's power. + Ability ability = new AttacksTriggeredAbility(new UnrulyKrasisEffect(), true); + ability.addTarget(new TargetControlledCreaturePermanent(StaticFilters.FILTER_ANOTHER_TARGET_CREATURE_YOU_CONTROL)); + this.addAbility(ability); + + // {3}{G}{U}: Adapt 3. + this.addAbility(new AdaptAbility(3, "{3}{G}{U}")); + } + + private UnrulyKrasis(final UnrulyKrasis card) { + super(card); + } + + @Override + public UnrulyKrasis copy() { + return new UnrulyKrasis(this); + } +} + +class UnrulyKrasisEffect extends OneShotEffect { + + UnrulyKrasisEffect() { + super(Outcome.BoostCreature); + staticText = "have the base power and toughness of another target creature you control " + + "become X/X until end of turn, where X is {this}'s power"; + } + + private UnrulyKrasisEffect(final UnrulyKrasisEffect effect) { + super(effect); + } + + @Override + public UnrulyKrasisEffect copy() { + return new UnrulyKrasisEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = source.getSourcePermanentOrLKI(game); + if (permanent == null) { + return false; + } + + int xValue = permanent.getPower().getValue(); + ContinuousEffect effect = new SetBasePowerToughnessTargetEffect(xValue, xValue, Duration.EndOfTurn); + effect.setTargetPointer(getTargetPointer().copy()); + game.addEffect(effect, source); + return true; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/u/UnscrupulousContractor.java b/Mage.Sets/src/mage/cards/u/UnscrupulousContractor.java new file mode 100644 index 00000000000..990f8461acb --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/UnscrupulousContractor.java @@ -0,0 +1,54 @@ +package mage.cards.u; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.effects.common.DoWhenCostPaid; +import mage.abilities.effects.common.DrawCardTargetEffect; +import mage.abilities.effects.common.LoseLifeTargetEffect; +import mage.abilities.keyword.PlotAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.target.TargetPlayer; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class UnscrupulousContractor extends CardImpl { + + public UnscrupulousContractor(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ASSASSIN); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // When Unscrupulous Contractor enters the battlefield, you may sacrifice a creature. When you do, target player draws two cards and loses 2 life. + ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility(new DrawCardTargetEffect(2), false); + ability.addEffect(new LoseLifeTargetEffect(2).setText("and loses 2 life")); + ability.addTarget(new TargetPlayer()); + this.addAbility(new EntersBattlefieldTriggeredAbility(new DoWhenCostPaid( + ability, new SacrificeTargetCost(StaticFilters.FILTER_PERMANENT_CREATURE), + "Sacrifice a creature?" + ))); + + // Plot {2}{B} + this.addAbility(new PlotAbility("{2}{B}")); + } + + private UnscrupulousContractor(final UnscrupulousContractor card) { + super(card); + } + + @Override + public UnscrupulousContractor copy() { + return new UnscrupulousContractor(this); + } +} diff --git a/Mage.Sets/src/mage/cards/u/UnyieldingGatekeeper.java b/Mage.Sets/src/mage/cards/u/UnyieldingGatekeeper.java new file mode 100644 index 00000000000..139dc896374 --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/UnyieldingGatekeeper.java @@ -0,0 +1,101 @@ +package mage.cards.u; + +import java.util.UUID; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.TurnedFaceUpSourceTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenControllerTargetPermanentEffect; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.effects.common.ExileThenReturnTargetEffect; +import mage.constants.Outcome; +import mage.constants.PutCards; +import mage.constants.SubType; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.keyword.DisguiseAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.common.FilterNonlandPermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.DetectiveToken; +import mage.target.common.TargetNonlandPermanent; + +/** + * @author Cguy7777 + */ +public final class UnyieldingGatekeeper extends CardImpl { + + private static final FilterNonlandPermanent filter = new FilterNonlandPermanent(); + + static { + filter.add(AnotherPredicate.instance); + } + + public UnyieldingGatekeeper(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.subtype.add(SubType.ELEPHANT); + this.subtype.add(SubType.CLERIC); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Disguise {1}{W} + this.addAbility(new DisguiseAbility(this, new ManaCostsImpl<>("{1}{W}"))); + + // When Unyielding Gatekeeper is turned face up, exile another target nonland permanent. + // If you controlled it, return it to the battlefield tapped. + // Otherwise, its controller creates a 2/2 white and blue Detective creature token. + Ability ability = new TurnedFaceUpSourceTriggeredAbility(new UnyieldingGatekeeperEffect()); + ability.addTarget(new TargetNonlandPermanent(filter)); + this.addAbility(ability); + } + + private UnyieldingGatekeeper(final UnyieldingGatekeeper card) { + super(card); + } + + @Override + public UnyieldingGatekeeper copy() { + return new UnyieldingGatekeeper(this); + } +} + +class UnyieldingGatekeeperEffect extends OneShotEffect { + + UnyieldingGatekeeperEffect() { + super(Outcome.Benefit); + staticText = "exile another target nonland permanent. " + + "If you controlled it, return it to the battlefield tapped. " + + "Otherwise, its controller creates a 2/2 white and blue Detective creature token"; + } + + private UnyieldingGatekeeperEffect(final UnyieldingGatekeeperEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent == null) { + return false; + } + + if (permanent.isControlledBy(source.getControllerId())) { + new ExileThenReturnTargetEffect( + false, false, PutCards.BATTLEFIELD_TAPPED).apply(game, source); + } else { + new ExileTargetEffect().apply(game, source); + new CreateTokenControllerTargetPermanentEffect(new DetectiveToken()).apply(game, source); + } + return true; + } + + @Override + public UnyieldingGatekeeperEffect copy() { + return new UnyieldingGatekeeperEffect(this); + } +} diff --git a/Mage.Sets/src/mage/cards/v/VadmirNewBlood.java b/Mage.Sets/src/mage/cards/v/VadmirNewBlood.java new file mode 100644 index 00000000000..7a5eaccf8cc --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VadmirNewBlood.java @@ -0,0 +1,62 @@ +package mage.cards.v; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.CommittedCrimeTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.SourceHasCounterCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.LifelinkAbility; +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.counters.CounterType; + +import java.util.UUID; + +/** + * @author PurpleCrowbar + */ +public final class VadmirNewBlood extends CardImpl { + + private static final Condition condition = new SourceHasCounterCondition(CounterType.P1P1, 4); + + public VadmirNewBlood(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.VAMPIRE, SubType.ROGUE); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Whenever you commit a crime, put a +1/+1 counter on Vadmir, New Blood. This ability triggers only once each turn. + this.addAbility(new CommittedCrimeTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()) + ).setTriggersOnceEachTurn(true)); + + // As long as Vadmir has four or more +1/+1 counters on it, it has menace and lifelink. + Ability ability = new SimpleStaticAbility(new ConditionalContinuousEffect(new GainAbilitySourceEffect( + new MenaceAbility(false), Duration.WhileOnBattlefield + ), condition, "As long as {this} has four or more +1/+1 counters on it, it has menace")); + ability.addEffect(new ConditionalContinuousEffect(new GainAbilitySourceEffect( + LifelinkAbility.getInstance(), Duration.WhileOnBattlefield + ), condition, "and lifelink")); + this.addAbility(ability); + } + + private VadmirNewBlood(final VadmirNewBlood card) { + super(card); + } + + @Override + public VadmirNewBlood copy() { + return new VadmirNewBlood(this); + } +} diff --git a/Mage.Sets/src/mage/cards/v/VadrokApexOfThunder.java b/Mage.Sets/src/mage/cards/v/VadrokApexOfThunder.java index 74832017430..8983c563218 100644 --- a/Mage.Sets/src/mage/cards/v/VadrokApexOfThunder.java +++ b/Mage.Sets/src/mage/cards/v/VadrokApexOfThunder.java @@ -3,16 +3,13 @@ package mage.cards.v; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.MutatesSourceTriggeredAbility; -import mage.abilities.effects.common.CastTargetForFreeEffect; +import mage.abilities.effects.common.MayCastTargetCardEffect; import mage.abilities.keyword.FirstStrikeAbility; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.MutateAbility; 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.constants.*; import mage.filter.FilterCard; import mage.filter.common.FilterNoncreatureCard; import mage.filter.predicate.mageobject.ManaValuePredicate; @@ -53,7 +50,7 @@ public final class VadrokApexOfThunder extends CardImpl { this.addAbility(FirstStrikeAbility.getInstance()); // Whenever this creature mutates, you may cast target noncreature card with converted mana cost 3 or less from your graveyard without paying its mana cost. - Ability ability = new MutatesSourceTriggeredAbility(new CastTargetForFreeEffect()); + Ability ability = new MutatesSourceTriggeredAbility(new MayCastTargetCardEffect(CastManaAdjustment.WITHOUT_PAYING_MANA_COST)); ability.addTarget(new TargetCardInYourGraveyard(filter)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/v/VanguardSuppressor.java b/Mage.Sets/src/mage/cards/v/VanguardSuppressor.java index 841e59e6542..68ad2d36e88 100644 --- a/Mage.Sets/src/mage/cards/v/VanguardSuppressor.java +++ b/Mage.Sets/src/mage/cards/v/VanguardSuppressor.java @@ -2,6 +2,7 @@ package mage.cards.v; import mage.MageInt; import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.SquadAbility; @@ -26,7 +27,7 @@ public final class VanguardSuppressor extends CardImpl { this.toughness = new MageInt(2); // Squad {2} - this.addAbility(new SquadAbility()); + this.addAbility(new SquadAbility(new GenericManaCost(2))); // Flying this.addAbility(FlyingAbility.getInstance()); diff --git a/Mage.Sets/src/mage/cards/v/Vault75MiddleSchool.java b/Mage.Sets/src/mage/cards/v/Vault75MiddleSchool.java new file mode 100644 index 00000000000..d1600bc74eb --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/Vault75MiddleSchool.java @@ -0,0 +1,57 @@ +package mage.cards.v; + +import mage.abilities.common.SagaAbility; +import mage.abilities.effects.common.ExileAllEffect; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.cards.*; +import mage.constants.*; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.PowerPredicate; +import java.util.UUID; + +/** + * + * @author DarkNik + */ +public final class Vault75MiddleSchool extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creatures with power 4 or greater"); + + static { + filter.add(new PowerPredicate(ComparisonType.MORE_THAN, 3)); + } + + public Vault75MiddleSchool(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 III.) + SagaAbility sagaAbility = new SagaAbility(this); + + // I -- Exile all creatures with power 4 or greater. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_I, + new ExileAllEffect(filter) + ); + + // II, III -- Put a +1/+1 counter on each creature you control. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_II, SagaChapter.CHAPTER_III, + new AddCountersAllEffect(CounterType.P1P1.createInstance(), StaticFilters.FILTER_CONTROLLED_CREATURE) + ); + + this.addAbility(sagaAbility); + } + + private Vault75MiddleSchool(final Vault75MiddleSchool card) { + super(card); + } + + @Override + public Vault75MiddleSchool copy() { + return new Vault75MiddleSchool(this); + } + +} diff --git a/Mage.Sets/src/mage/cards/v/VaultPlunderer.java b/Mage.Sets/src/mage/cards/v/VaultPlunderer.java new file mode 100644 index 00000000000..25d96b4c814 --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VaultPlunderer.java @@ -0,0 +1,44 @@ +package mage.cards.v; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.DrawCardTargetEffect; +import mage.abilities.effects.common.LoseLifeTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.TargetPlayer; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class VaultPlunderer extends CardImpl { + + public VaultPlunderer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(3); + this.toughness = new MageInt(1); + + // When Vault Plunderer enters the battlefield, target player draws a card and loses 1 life. + Ability ability = new EntersBattlefieldTriggeredAbility(new DrawCardTargetEffect(1)); + ability.addEffect(new LoseLifeTargetEffect(1).setText("and loses 1 life")); + ability.addTarget(new TargetPlayer()); + this.addAbility(ability); + } + + private VaultPlunderer(final VaultPlunderer card) { + super(card); + } + + @Override + public VaultPlunderer copy() { + return new VaultPlunderer(this); + } +} diff --git a/Mage.Sets/src/mage/cards/v/VaultbornTyrant.java b/Mage.Sets/src/mage/cards/v/VaultbornTyrant.java new file mode 100644 index 00000000000..ac42c798ab2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VaultbornTyrant.java @@ -0,0 +1,110 @@ +package mage.cards.v; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DiesSourceTriggeredAbility; +import mage.abilities.common.EntersBattlefieldThisOrAnotherTriggeredAbility; +import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenCopyTargetEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.PowerPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.PermanentToken; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class VaultbornTyrant extends CardImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent("creature with power 4 or greater"); + + static { + filter.add(new PowerPredicate(ComparisonType.OR_GREATER, 4)); + } + + public VaultbornTyrant(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{G}{G}"); + + this.subtype.add(SubType.DINOSAUR); + this.power = new MageInt(6); + this.toughness = new MageInt(6); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Whenever Vaultborn Tyrant or another creature with power 4 or greater enters the battlefield under your control, you gain 3 life and draw a card. + Ability ability = new EntersBattlefieldThisOrAnotherTriggeredAbility( + new GainLifeEffect(3), filter, false, true + ); + ability.addEffect(new DrawCardSourceControllerEffect(1).concatBy("and")); + this.addAbility(ability); + + // When Vaultborn Tyrant dies, if it's not a token, create a token that's a copy of it, except it's an artifact in addition to its other types. + this.addAbility(new ConditionalInterveningIfTriggeredAbility( + new DiesSourceTriggeredAbility( + new VaultbornTyrantCreateCopyEffect(), + false + ), + VaultbornTyrant::checkSource, + "When {this} dies, if it's not a token, create a token that's a copy of it, " + + "except it's an artifact in addition to its other types." + )); + } + + private VaultbornTyrant(final VaultbornTyrant card) { + super(card); + } + + @Override + public VaultbornTyrant copy() { + return new VaultbornTyrant(this); + } + + static boolean checkSource(Game game, Ability source) { + return !(source.getSourcePermanentOrLKI(game) instanceof PermanentToken); + } +} + +class VaultbornTyrantCreateCopyEffect extends OneShotEffect { + + VaultbornTyrantCreateCopyEffect() { + super(Outcome.PutCreatureInPlay); + } + + private VaultbornTyrantCreateCopyEffect(final VaultbornTyrantCreateCopyEffect effect) { + super(effect); + } + + @Override + public VaultbornTyrantCreateCopyEffect copy() { + return new VaultbornTyrantCreateCopyEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanentOrLKIBattlefield(source.getSourceId()); + if (permanent == null) { + return false; + } + CreateTokenCopyTargetEffect effect = new CreateTokenCopyTargetEffect( + source.getControllerId(), CardType.ARTIFACT, false + ); + effect.setTargetPointer(new FixedTarget(source.getSourceId(), game)); + return effect.apply(game, source); + } +} diff --git a/Mage.Sets/src/mage/cards/v/VengefulPharaoh.java b/Mage.Sets/src/mage/cards/v/VengefulPharaoh.java index 0e575424ecc..98f75e574e1 100644 --- a/Mage.Sets/src/mage/cards/v/VengefulPharaoh.java +++ b/Mage.Sets/src/mage/cards/v/VengefulPharaoh.java @@ -2,7 +2,6 @@ package mage.cards.v; import mage.MageInt; -import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.OneShotEffect; @@ -15,10 +14,8 @@ import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.Zone; import mage.game.Game; -import mage.game.events.DamagedEvent; -import mage.game.events.GameEvent; +import mage.game.events.*; import mage.game.permanent.Permanent; -import mage.game.turn.Step; import mage.players.Player; import mage.target.common.TargetAttackingCreature; @@ -55,12 +52,6 @@ public final class VengefulPharaoh extends CardImpl { class VengefulPharaohTriggeredAbility extends TriggeredAbilityImpl { - Step stepTriggeredPlayer; - int turnTriggeredPlayer; - - Step stepTriggeredPlansewalker; - int turnTriggeredPlaneswalker; - public VengefulPharaohTriggeredAbility() { super(Zone.GRAVEYARD, new VengefulPharaohEffect(), false); this.addTarget(new TargetAttackingCreature()); @@ -68,10 +59,6 @@ class VengefulPharaohTriggeredAbility extends TriggeredAbilityImpl { private VengefulPharaohTriggeredAbility(final VengefulPharaohTriggeredAbility ability) { super(ability); - this.stepTriggeredPlansewalker = ability.stepTriggeredPlansewalker; - this.turnTriggeredPlaneswalker = ability.turnTriggeredPlaneswalker; - this.stepTriggeredPlayer = ability.stepTriggeredPlayer; - this.turnTriggeredPlayer = ability.turnTriggeredPlayer; } @Override @@ -81,48 +68,46 @@ class VengefulPharaohTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkInterveningIfClause(Game game) { - // 9/22/2011 - If multiple creatures deal combat damage to you and to a planeswalker you control - // simultaneously, Vengeful Pharaoh will trigger twice. The first trigger will cause Vengeful Pharaoh - // to be put on top of your library. The second trigger will then do nothing, as Vengeful Pharaoh is - // no longer in your graveyard when it tries to resolve. Note that the second trigger will do nothing - // even if Vengeful Pharaoh is put back into your graveyard before it tries to resolve, as it's a - // different Vengeful Pharaoh than the one that was there before. - MageObjectReference mor = new MageObjectReference(getSourceId(), game); - return mor.refersTo(this.getSourceObject(game), game); + // Vengeful Pharaoh must be in your graveyard when combat damage is dealt to you or a planeswalker you control + // in order for its ability to trigger. That is, it can’t die and trigger from your graveyard during the same + // combat damage step. (2011-09-22) + + // If Vengeful Pharaoh is no longer in your graveyard when the triggered ability would resolve, the triggered + // ability won’t do anything. (2011-09-22) + return game.getState().getZone(getSourceId()) == Zone.GRAVEYARD; } @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PLAYER - || event.getType() == GameEvent.EventType.DAMAGED_PERMANENT; + // If multiple creatures deal combat damage to you simultaneously, Vengeful Pharaoh will only trigger once. + // (2011-09-22) + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PLAYER + || event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT; } @Override public boolean checkTrigger(GameEvent event, Game game) { - if ((event.getType() == GameEvent.EventType.DAMAGED_PLAYER && event.getTargetId().equals(this.getControllerId())) - && ((DamagedEvent) event).isCombatDamage()) { - if (!game.getPhase().getStep().equals(stepTriggeredPlayer) || game.getTurnNum() != turnTriggeredPlayer) { - stepTriggeredPlayer = game.getPhase().getStep(); - turnTriggeredPlayer = game.getTurnNum(); - return true; - } + + if ((event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PLAYER + && event.getTargetId().equals(this.getControllerId()))) { + DamagedBatchForOnePlayerEvent dEvent = (DamagedBatchForOnePlayerEvent) event; + return dEvent.isCombatDamage() && dEvent.getAmount() > 0; } - if (event.getType() == GameEvent.EventType.DAMAGED_PERMANENT && ((DamagedEvent) event).isCombatDamage()) { + if (event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT) { Permanent permanent = game.getPermanent(event.getTargetId()); - if (permanent != null && permanent.isPlaneswalker(game) && permanent.isControlledBy(this.getControllerId())) { - if (!game.getPhase().getStep().equals(stepTriggeredPlansewalker) || game.getTurnNum() != turnTriggeredPlaneswalker) { - stepTriggeredPlansewalker = game.getPhase().getStep(); - turnTriggeredPlaneswalker = game.getTurnNum(); - return true; - } - } + DamagedBatchForOnePermanentEvent dEvent = (DamagedBatchForOnePermanentEvent) event; + return permanent != null + && permanent.isPlaneswalker(game) + && permanent.isControlledBy(this.getControllerId()) + && dEvent.isCombatDamage() && dEvent.getAmount() > 0; } return false; } @Override public String getRule() { - return "Whenever combat damage is dealt to you or a planeswalker you control, if {this} is in your graveyard, destroy target attacking creature, then put {this} on top of your library."; + return "Whenever combat damage is dealt to you or a planeswalker you control, if {this} is in your " + + "graveyard, destroy target attacking creature, then put {this} on top of your library."; } } @@ -148,9 +133,13 @@ class VengefulPharaohEffect extends OneShotEffect { Card card = game.getCard(source.getSourceId()); if (card != null && controller != null) { Permanent permanent = game.getPermanent(source.getFirstTarget()); - if (permanent != null) { - permanent.destroy(source, game, false); + if (permanent == null) { + // If the attacking creature is an illegal target when the triggered ability tries to resolve, + // it won’t resolve and none of its effects will happen. Vengeful Pharaoh will remain in your graveyard. + // (2011-09-22) + return false; } + permanent.destroy(source, game, false); controller.moveCardToLibraryWithInfo(card, source, game, Zone.GRAVEYARD, true, true); return true; } diff --git a/Mage.Sets/src/mage/cards/v/VengefulRegrowth.java b/Mage.Sets/src/mage/cards/v/VengefulRegrowth.java new file mode 100644 index 00000000000..788867f913a --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VengefulRegrowth.java @@ -0,0 +1,106 @@ +package mage.cards.v; + +import mage.abilities.Ability; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.FlashbackAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.filter.common.FilterLandCard; +import mage.game.Game; +import mage.game.permanent.token.PlantWarriorToken; +import mage.players.Player; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.Objects; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * @author Susucr + */ +public final class VengefulRegrowth extends CardImpl { + + private static final FilterCard filter = new FilterLandCard("land cards from your graveyard"); + + public VengefulRegrowth(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{G}{G}"); + + // Return up to three target land cards from your graveyard to the battlefield tapped. Create that many 4/2 green Plant Warrior creature tokens with reach. + this.getSpellAbility().addEffect(new VengefulRegrowthEffect()); + this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(0, 3, filter)); + + // Flashback {6}{G}{G} + this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{6}{G}{G}"))); + } + + private VengefulRegrowth(final VengefulRegrowth card) { + super(card); + } + + @Override + public VengefulRegrowth copy() { + return new VengefulRegrowth(this); + } +} + +class VengefulRegrowthEffect extends OneShotEffect { + + VengefulRegrowthEffect() { + super(Outcome.PutLandInPlay); + staticText = "Return up to three target land cards from your graveyard to the battlefield tapped. " + + "Create that many 4/2 green Plant Warrior creature tokens with reach."; + } + + private VengefulRegrowthEffect(final VengefulRegrowthEffect effect) { + super(effect); + } + + @Override + public VengefulRegrowthEffect copy() { + return new VengefulRegrowthEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + Cards cards = new CardsImpl( + getTargetPointer() + .getTargets(game, source) + .stream() + .map(game::getCard) + .filter(Objects::nonNull) + .collect(Collectors.toList()) + ); + if (cards.isEmpty()) { + return false; + } + + controller.moveCards(cards.getCards(game), Zone.BATTLEFIELD, source, game, true, false, false, null); + + // Release note: + // + // The number of Plant Warrior tokens created is equal to the number of land cards returned + // from your graveyard to the battlefield by Vengeful Regrowth. For example, if you cast + // Vengeful Regrowth targeting three Forest cards in your graveyard but two of those Forest + // cards are exiled in response, you'll return the remaining Forest card and create one Plant Warrior token. + cards.retainZone(Zone.BATTLEFIELD, game); + int returned = cards.size(); + if (returned > 0) { + new CreateTokenEffect(new PlantWarriorToken(), returned) + .apply(game, source); + } + return true; + } + +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/v/VengefulTownsfolk.java b/Mage.Sets/src/mage/cards/v/VengefulTownsfolk.java new file mode 100644 index 00000000000..cad0acb0dfa --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VengefulTownsfolk.java @@ -0,0 +1,52 @@ +package mage.cards.v; + +import mage.MageInt; +import mage.abilities.common.DiesOneOrMoreCreatureTriggeredAbility; +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.TargetController; +import mage.counters.CounterType; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class VengefulTownsfolk extends CardImpl { + + public static final FilterCreaturePermanent filter = new FilterCreaturePermanent("other creatures you control"); + + static { + filter.add(AnotherPredicate.instance); + filter.add(TargetController.YOU.getControllerPredicate()); + } + + public VengefulTownsfolk(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.CITIZEN); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Whenever one or more other creatures you control die, put a +1/+1 counter on Vengeful Townsfolk. + this.addAbility(new DiesOneOrMoreCreatureTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance(1)), + filter + )); + } + + private VengefulTownsfolk(final VengefulTownsfolk card) { + super(card); + } + + @Override + public VengefulTownsfolk copy() { + return new VengefulTownsfolk(this); + } +} diff --git a/Mage.Sets/src/mage/cards/v/VergeRangers.java b/Mage.Sets/src/mage/cards/v/VergeRangers.java index 09ddb5644ff..b3b44007daa 100644 --- a/Mage.Sets/src/mage/cards/v/VergeRangers.java +++ b/Mage.Sets/src/mage/cards/v/VergeRangers.java @@ -4,14 +4,12 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; -import mage.abilities.effects.common.continuous.PlayTheTopCardEffect; +import mage.abilities.effects.common.continuous.PlayFromTopOfLibraryEffect; import mage.abilities.keyword.FirstStrikeAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.TargetController; -import mage.constants.Zone; import mage.filter.FilterCard; import mage.filter.StaticFilters; import mage.filter.common.FilterLandCard; @@ -41,7 +39,7 @@ public final class VergeRangers extends CardImpl { this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); // As long as an opponent controls more lands than you, you may play lands from the top of your library. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new VergeRangersEffect())); + this.addAbility(new SimpleStaticAbility(new VergeRangersEffect())); } private VergeRangers(final VergeRangers card) { @@ -54,12 +52,12 @@ public final class VergeRangers extends CardImpl { } } -class VergeRangersEffect extends PlayTheTopCardEffect { +class VergeRangersEffect extends PlayFromTopOfLibraryEffect { private static final FilterCard filter = new FilterLandCard("play lands"); - public VergeRangersEffect() { - super(TargetController.YOU, filter, false); + VergeRangersEffect() { + super(filter); staticText = "As long as an opponent controls more lands than you, you may play lands from the top of your library"; } @@ -85,4 +83,4 @@ class VergeRangersEffect extends PlayTheTopCardEffect { .orElse(0); return maxOpponentLandCount > myLandCount; } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/v/VialSmasherGleefulGrenadier.java b/Mage.Sets/src/mage/cards/v/VialSmasherGleefulGrenadier.java new file mode 100644 index 00000000000..f980047586e --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VialSmasherGleefulGrenadier.java @@ -0,0 +1,55 @@ +package mage.cards.v; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; +import mage.abilities.effects.common.DamageTargetEffect; +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.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.filter.predicate.mageobject.OutlawPredicate; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class VialSmasherGleefulGrenadier extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("another outlaw"); + + static { + filter.add(AnotherPredicate.instance); + filter.add(OutlawPredicate.instance); + } + + public VialSmasherGleefulGrenadier(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{B}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.GOBLIN); + this.subtype.add(SubType.MERCENARY); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Whenever another outlaw enters the battlefield under your control, Vial Smasher, Gleeful Grenadier deals 1 damage to target opponent. + Ability ability = new EntersBattlefieldControlledTriggeredAbility(new DamageTargetEffect(1), filter); + ability.addTarget(new TargetOpponent()); + this.addAbility(ability); + } + + private VialSmasherGleefulGrenadier(final VialSmasherGleefulGrenadier card) { + super(card); + } + + @Override + public VialSmasherGleefulGrenadier copy() { + return new VialSmasherGleefulGrenadier(this); + } +} diff --git a/Mage.Sets/src/mage/cards/v/VigilForTheLost.java b/Mage.Sets/src/mage/cards/v/VigilForTheLost.java index ec692676a8a..b1dea4e1a2c 100644 --- a/Mage.Sets/src/mage/cards/v/VigilForTheLost.java +++ b/Mage.Sets/src/mage/cards/v/VigilForTheLost.java @@ -1,33 +1,29 @@ - - package mage.cards.v; -import java.util.UUID; - import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.common.DiesCreatureTriggeredAbility; +import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.OneShotEffect; 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.game.events.GameEvent; -import mage.game.events.ZoneChangeEvent; -import mage.game.permanent.Permanent; import mage.players.Player; +import java.util.UUID; + /** - * @author Loki + * @author xenohedron */ public final class VigilForTheLost extends CardImpl { public VigilForTheLost(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{W}"); - this.addAbility(new VigilForTheLostTriggeredAbility()); + // Whenever a creature you control dies, you may pay {X}. If you do, you gain X life. + this.addAbility(new DiesCreatureTriggeredAbility(new VigilForTheLostEffect(), true, StaticFilters.FILTER_CONTROLLED_A_CREATURE)); } private VigilForTheLost(final VigilForTheLost card) { @@ -41,47 +37,10 @@ public final class VigilForTheLost extends CardImpl { } -class VigilForTheLostTriggeredAbility extends TriggeredAbilityImpl { - VigilForTheLostTriggeredAbility() { - super(Zone.BATTLEFIELD, new VigilForTheLostEffect()); - } - - private VigilForTheLostTriggeredAbility(final VigilForTheLostTriggeredAbility ability) { - super(ability); - } - - @Override - public VigilForTheLostTriggeredAbility copy() { - return new VigilForTheLostTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.ZONE_CHANGE; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - ZoneChangeEvent zoneChangeEvent = (ZoneChangeEvent) event; - if (zoneChangeEvent.isDiesEvent()) { - Permanent p = (Permanent) game.getLastKnownInformation(event.getTargetId(), Zone.BATTLEFIELD); - if (p.isControlledBy(this.getControllerId()) && p.isCreature(game)) { - return true; - } - } - return false; - } - - @Override - public String getRule() { - return "Whenever a creature you control is put into a graveyard from the battlefield, you may pay {X}. If you do, you gain X life."; - } -} - class VigilForTheLostEffect extends OneShotEffect { VigilForTheLostEffect() { super(Outcome.GainLife); - staticText = "you may pay {X}. If you do, you gain X life"; + staticText = "pay {X}. If you do, you gain X life"; } private VigilForTheLostEffect(final VigilForTheLostEffect effect) { @@ -90,17 +49,16 @@ class VigilForTheLostEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - ManaCostsImpl cost = new ManaCostsImpl<>("{X}"); - cost.clearPaid(); - if (cost.payOrRollback(source, game, source, source.getControllerId())) { - Player player = game.getPlayer(source.getControllerId()); - if (player != null) { - player.gainLife(cost.getX(), game, source); - return true; - } + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + int costX = controller.announceXMana(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source); + if (new GenericManaCost(costX).pay(source, game, source, source.getControllerId(), false, null)) { + controller.gainLife(costX, game, source); + return true; } return false; - } @Override @@ -108,4 +66,4 @@ class VigilForTheLostEffect extends OneShotEffect { return new VigilForTheLostEffect(this); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/v/VileEntomber.java b/Mage.Sets/src/mage/cards/v/VileEntomber.java index 144ad9a5c6c..d338cd86553 100644 --- a/Mage.Sets/src/mage/cards/v/VileEntomber.java +++ b/Mage.Sets/src/mage/cards/v/VileEntomber.java @@ -28,7 +28,7 @@ public final class VileEntomber extends CardImpl { this.addAbility(DeathtouchAbility.getInstance()); // When Vile Entomber enters the battlefield, search your library for a card, put that card into your graveyard, then shuffle. - this.addAbility(new EntersBattlefieldTriggeredAbility(new SearchLibraryPutInGraveyardEffect())); + this.addAbility(new EntersBattlefieldTriggeredAbility(new SearchLibraryPutInGraveyardEffect(true))); } private VileEntomber(final VileEntomber card) { diff --git a/Mage.Sets/src/mage/cards/v/VisageBandit.java b/Mage.Sets/src/mage/cards/v/VisageBandit.java new file mode 100644 index 00000000000..9853d2f21ee --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VisageBandit.java @@ -0,0 +1,60 @@ +package mage.cards.v; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.effects.common.CopyPermanentEffect; +import mage.abilities.keyword.PlotAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.util.functions.CopyApplier; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class VisageBandit extends CardImpl { + + public VisageBandit(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}"); + + this.subtype.add(SubType.SHAPESHIFTER); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // You may have Visage Bandit enter the battlefield as a copy of a creature you control, except it's a Shapeshifter Rogue in addition to its other types. + this.addAbility(new EntersBattlefieldAbility( + new CopyPermanentEffect(StaticFilters.FILTER_CONTROLLED_CREATURE, new VisageBanditCopyApplier()), + true, null, "You may have {this} enter the battlefield as a copy of " + + "a creature you control, except it's a Shapeshifter Rogue in addition to its other types.", "" + )); + + // Plot {2}{U} + this.addAbility(new PlotAbility("{2}{U}")); + } + + private VisageBandit(final VisageBandit card) { + super(card); + } + + @Override + public VisageBandit copy() { + return new VisageBandit(this); + } +} + +class VisageBanditCopyApplier extends CopyApplier { + + @Override + public boolean apply(Game game, MageObject blueprint, Ability source, UUID copyToObjectId) { + blueprint.addSubType(SubType.SHAPESHIFTER, SubType.ROGUE); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/v/VivienMonstersAdvocate.java b/Mage.Sets/src/mage/cards/v/VivienMonstersAdvocate.java index 164bef33b1b..47138bda3a1 100644 --- a/Mage.Sets/src/mage/cards/v/VivienMonstersAdvocate.java +++ b/Mage.Sets/src/mage/cards/v/VivienMonstersAdvocate.java @@ -7,7 +7,7 @@ import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; -import mage.abilities.effects.common.continuous.PlayTheTopCardEffect; +import mage.abilities.effects.common.continuous.PlayFromTopOfLibraryEffect; import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -47,7 +47,7 @@ public final class VivienMonstersAdvocate extends CardImpl { this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); // You may cast creature spells from the top of your library. - this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(TargetController.YOU, filter, false))); + this.addAbility(new SimpleStaticAbility(new PlayFromTopOfLibraryEffect(filter))); // +1: Create a 3/3 green Beast creature token. Put your choice of a vigilance // counter, a reach counter, or a trample counter on it. diff --git a/Mage.Sets/src/mage/cards/v/VizierOfTheMenagerie.java b/Mage.Sets/src/mage/cards/v/VizierOfTheMenagerie.java index 52c6f708348..b560d5ff305 100644 --- a/Mage.Sets/src/mage/cards/v/VizierOfTheMenagerie.java +++ b/Mage.Sets/src/mage/cards/v/VizierOfTheMenagerie.java @@ -7,7 +7,7 @@ import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.AsThoughEffectImpl; import mage.abilities.effects.AsThoughManaEffect; import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; -import mage.abilities.effects.common.continuous.PlayTheTopCardEffect; +import mage.abilities.effects.common.continuous.PlayFromTopOfLibraryEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; @@ -37,9 +37,7 @@ public final class VizierOfTheMenagerie extends CardImpl { this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); // You may cast creature spells from the top of your library. - this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect( - TargetController.YOU, filter, false - ))); + this.addAbility(new SimpleStaticAbility(new PlayFromTopOfLibraryEffect(filter))); // You may spend mana as though it were mana of any type to cast creature spells. this.addAbility(new SimpleStaticAbility(new VizierOfTheMenagerieManaEffect())); diff --git a/Mage.Sets/src/mage/cards/v/VoharVodalianDesecrator.java b/Mage.Sets/src/mage/cards/v/VoharVodalianDesecrator.java index 6a32cb79806..3fd8800045a 100644 --- a/Mage.Sets/src/mage/cards/v/VoharVodalianDesecrator.java +++ b/Mage.Sets/src/mage/cards/v/VoharVodalianDesecrator.java @@ -1,6 +1,5 @@ package mage.cards.v; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.ActivateAsSorceryActivatedAbility; @@ -8,19 +7,20 @@ 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.MayCastTargetThenExileEffect; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.MayCastTargetCardEffect; import mage.cards.Card; -import mage.constants.*; 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.common.TargetCardInYourGraveyard; +import java.util.UUID; + /** - * * @author weirddan455 */ public final class VoharVodalianDesecrator extends CardImpl { @@ -40,7 +40,7 @@ public final class VoharVodalianDesecrator extends CardImpl { // {2}, Sacrifice Vohar, Vodalian Desecrator: You may cast target instant or sorcery card from your graveyard this turn. If that spell would be put into your graveyard, exile it instead. Activate only as a sorcery. Ability ability = new ActivateAsSorceryActivatedAbility( - new MayCastTargetThenExileEffect(Duration.EndOfTurn), new GenericManaCost(2) + new MayCastTargetCardEffect(Duration.EndOfTurn, true), new GenericManaCost(2) ); ability.addCost(new SacrificeSourceCost()); ability.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY_FROM_YOUR_GRAVEYARD)); diff --git a/Mage.Sets/src/mage/cards/v/VojaJawsOfTheConclave.java b/Mage.Sets/src/mage/cards/v/VojaJawsOfTheConclave.java index abe72d6c645..90a07a38ed1 100644 --- a/Mage.Sets/src/mage/cards/v/VojaJawsOfTheConclave.java +++ b/Mage.Sets/src/mage/cards/v/VojaJawsOfTheConclave.java @@ -58,8 +58,7 @@ public final class VojaJawsOfTheConclave extends CardImpl { // where X is the number of Elves you control. Draw a card for each Wolf you control. Ability ability = new AttacksTriggeredAbility( new AddCountersAllEffect( - // Set amount to 0, otherwise AddCountersAllEffect.apply() will default to amount = 1 if xValueElves = 0 - CounterType.P1P1.createInstance(0), + CounterType.P1P1.createInstance(), xValueElves, StaticFilters.FILTER_CONTROLLED_CREATURE ).setText("put X +1/+1 counters on each creature you control, where X is the number of Elves you control")); diff --git a/Mage.Sets/src/mage/cards/v/VoraciousVarmint.java b/Mage.Sets/src/mage/cards/v/VoraciousVarmint.java new file mode 100644 index 00000000000..120056351e9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VoraciousVarmint.java @@ -0,0 +1,49 @@ +package mage.cards.v; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class VoraciousVarmint extends CardImpl { + + public VoraciousVarmint(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); + + this.subtype.add(SubType.VARMINT); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // {1}, Sacrifice Voracious Varmint: Destroy target artifact or enchantment. + Ability ability = new SimpleActivatedAbility(new DestroyTargetEffect(), new GenericManaCost(1)); + ability.addCost(new SacrificeSourceCost()); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_ARTIFACT_OR_ENCHANTMENT)); + this.addAbility(ability); + } + + private VoraciousVarmint(final VoraciousVarmint card) { + super(card); + } + + @Override + public VoraciousVarmint copy() { + return new VoraciousVarmint(this); + } +} diff --git a/Mage.Sets/src/mage/cards/v/VraskaJoinsUp.java b/Mage.Sets/src/mage/cards/v/VraskaJoinsUp.java new file mode 100644 index 00000000000..717b8911e64 --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VraskaJoinsUp.java @@ -0,0 +1,48 @@ +package mage.cards.v; + +import mage.abilities.common.DealsDamageToAPlayerAllTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SetTargetPointer; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class VraskaJoinsUp extends CardImpl { + + public VraskaJoinsUp(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{B}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + + // When Vraska Joins Up enters the battlefield, put a deathtouch counter on each creature you control. + this.addAbility(new EntersBattlefieldTriggeredAbility(new AddCountersAllEffect( + CounterType.DEATHTOUCH.createInstance(), StaticFilters.FILTER_CONTROLLED_CREATURE + ))); + + // Whenever a legendary creature you control deals combat damage to a player, draw a card. + this.addAbility(new DealsDamageToAPlayerAllTriggeredAbility( + new DrawCardSourceControllerEffect(1), + StaticFilters.FILTER_CONTROLLED_CREATURE_LEGENDARY, + false, SetTargetPointer.NONE, true + )); + } + + private VraskaJoinsUp(final VraskaJoinsUp card) { + super(card); + } + + @Override + public VraskaJoinsUp copy() { + return new VraskaJoinsUp(this); + } +} diff --git a/Mage.Sets/src/mage/cards/v/VraskaTheSilencer.java b/Mage.Sets/src/mage/cards/v/VraskaTheSilencer.java new file mode 100644 index 00000000000..6cf653b74eb --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VraskaTheSilencer.java @@ -0,0 +1,164 @@ +package mage.cards.v; + +import mage.MageInt; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.common.DiesCreatureTriggeredAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.keyword.DeathtouchAbility; +import mage.abilities.token.TreasureAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class VraskaTheSilencer extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("a nontoken creature an opponent controls"); + + static { + filter.add(TokenPredicate.FALSE); + filter.add(TargetController.OPPONENT.getControllerPredicate()); + } + + public VraskaTheSilencer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.GORGON); + this.subtype.add(SubType.ASSASSIN); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Deathtouch + this.addAbility(DeathtouchAbility.getInstance()); + + // Whenever a nontoken creature an opponent controls dies, you may pay {1}. If you do, return that card to the battlefield tapped under your control. It's a Treasure artifact with "{T}, Sacrifice this artifact: Add one mana of any color," and it loses all other card types. + this.addAbility(new DiesCreatureTriggeredAbility( + new DoIfCostPaid(new VraskaTheSilencerEffect(), new GenericManaCost(1)), + false, filter, true + )); + } + + private VraskaTheSilencer(final VraskaTheSilencer card) { + super(card); + } + + @Override + public VraskaTheSilencer copy() { + return new VraskaTheSilencer(this); + } +} + +class VraskaTheSilencerEffect extends OneShotEffect { + + VraskaTheSilencerEffect() { + super(Outcome.PutCreatureInPlay); + staticText = "return that card to the battlefield tapped under your control. " + + "It's a Treasure artifact with \"{T}, Sacrifice this artifact: Add one mana of any color,\" " + + "and it loses all other card types."; + } + + private VraskaTheSilencerEffect(final VraskaTheSilencerEffect effect) { + super(effect); + } + + @Override + public VraskaTheSilencerEffect copy() { + return new VraskaTheSilencerEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + Card card = game.getCard(getTargetPointer().getFirst(game, source)); + if (card == null) { + return false; + } + // Apply the continuous effect before moving the card to have the proper types on entering. + ContinuousEffect continuousEffect = new VraskaTheSilencerContinuousEffect(new MageObjectReference(card, game, 1)); + game.addEffect(continuousEffect, source); + controller.moveCards(card, Zone.BATTLEFIELD, source, game, true, false, false, null); + return true; + } +} + +class VraskaTheSilencerContinuousEffect extends ContinuousEffectImpl { + + private final MageObjectReference mor; + private static final Ability ability = new TreasureAbility(false); + + VraskaTheSilencerContinuousEffect(MageObjectReference mor) { + super(Duration.WhileOnBattlefield, Outcome.Neutral); + this.staticText = "It's a Treasure artifact and loses all other card types."; + this.mor = mor; + dependencyTypes.add(DependencyType.ArtifactAddingRemoving); + } + + private VraskaTheSilencerContinuousEffect(final VraskaTheSilencerContinuousEffect effect) { + super(effect); + this.mor = effect.mor; + } + + @Override + public boolean apply(Game game, Ability source) { + return false; + } + + @Override + public VraskaTheSilencerContinuousEffect copy() { + return new VraskaTheSilencerContinuousEffect(this); + } + + @Override + public void init(Ability source, Game game) { + super.init(source, game); + affectedObjectList.add(mor); + } + + @Override + public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { + Permanent permanent = affectedObjectList.get(0).getPermanent(game); + if (permanent == null) { + this.discard(); + return false; + } + switch (layer) { + case TypeChangingEffects_4: + permanent.setIsAllCreatureTypes(game, false); + permanent.retainAllArtifactSubTypes(game); + permanent.removeAllCardTypes(game); + permanent.addCardType(game, CardType.ARTIFACT); + permanent.addSubType(game, SubType.TREASURE); + break; + case AbilityAddingRemovingEffects_6: + permanent.addAbility(ability, source.getSourceId(), game); + break; + } + return true; + } + + + @Override + public boolean hasLayer(Layer layer) { + return layer == Layer.AbilityAddingRemovingEffects_6 + || layer == Layer.TypeChangingEffects_4; + } +} diff --git a/Mage.Sets/src/mage/cards/v/VronosMaskedInquisitor.java b/Mage.Sets/src/mage/cards/v/VronosMaskedInquisitor.java index 5ce41e937d5..1eb650eae34 100644 --- a/Mage.Sets/src/mage/cards/v/VronosMaskedInquisitor.java +++ b/Mage.Sets/src/mage/cards/v/VronosMaskedInquisitor.java @@ -1,6 +1,5 @@ package mage.cards.v; -import mage.abilities.Ability; import mage.abilities.LoyaltyAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; @@ -19,17 +18,13 @@ 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.FilterControlledPlaneswalkerPermanent; -import mage.filter.common.FilterNonlandPermanent; import mage.filter.predicate.mageobject.AnotherPredicate; -import mage.filter.predicate.permanent.ControllerIdPredicate; -import mage.game.Game; import mage.game.permanent.token.custom.CreatureToken; -import mage.players.Player; import mage.target.TargetPermanent; -import mage.target.targetadjustment.TargetAdjuster; +import mage.target.common.TargetNonlandPermanent; +import mage.target.targetadjustment.EachOpponentPermanentTargetsAdjuster; import mage.target.targetpointer.EachTargetPointer; import java.util.UUID; @@ -61,7 +56,7 @@ public final class VronosMaskedInquisitor extends CardImpl { // −2: For each opponent, return up to one target nonland permanent that player controls to its owner's hand. LoyaltyAbility ability2 = new LoyaltyAbility(new ReturnToHandTargetEffect().setTargetPointer(new EachTargetPointer()) .setText("for each opponent, return up to one target nonland permanent that player controls to its owner's hand"), -2); - ability2.setTargetAdjuster(VronosMaskedInquisitorAdjuster.instance); + ability2.setTargetAdjuster(new EachOpponentPermanentTargetsAdjuster(new TargetNonlandPermanent(0,1))); this.addAbility(ability2); // −7: Target artifact you control becomes a 9/9 Construct artifact creature and gains vigilance, indestructible, and "This creature can't be blocked." @@ -86,21 +81,3 @@ public final class VronosMaskedInquisitor extends CardImpl { return new VronosMaskedInquisitor(this); } } - -enum VronosMaskedInquisitorAdjuster implements TargetAdjuster { - instance; - - @Override - public void adjustTargets(Ability ability, Game game) { - ability.getTargets().clear(); - for (UUID opponentId : game.getOpponents(ability.getControllerId())) { - Player opponent = game.getPlayer(opponentId); - if (opponent == null) { - continue; - } - FilterPermanent filter = new FilterNonlandPermanent("nonland permanent controlled by " + opponent.getLogName()); - filter.add(new ControllerIdPredicate(opponentId)); - ability.addTarget(new TargetPermanent(0, 1, filter, false)); - } - } -} diff --git a/Mage.Sets/src/mage/cards/w/WallOfEssence.java b/Mage.Sets/src/mage/cards/w/WallOfEssence.java index 25c52da13c5..e1d99e68312 100644 --- a/Mage.Sets/src/mage/cards/w/WallOfEssence.java +++ b/Mage.Sets/src/mage/cards/w/WallOfEssence.java @@ -13,9 +13,8 @@ import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.Zone; import mage.game.Game; -import mage.game.events.DamagedEvent; +import mage.game.events.DamagedBatchForOnePermanentEvent; import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; import mage.players.Player; import java.util.UUID; @@ -66,20 +65,20 @@ class WallOfEssenceTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT; } @Override public boolean checkTrigger(GameEvent event, Game game) { - Permanent permanent = game.getPermanent(event.getTargetId()); - if (permanent == null - || !permanent.isCreature(game) - || !event.getTargetId().equals(this.sourceId) - || !((DamagedEvent) event).isCombatDamage()) { - return false; + + DamagedBatchForOnePermanentEvent dEvent = (DamagedBatchForOnePermanentEvent) event; + int damage = dEvent.getAmount(); + + if (event.getTargetId().equals(this.sourceId) && dEvent.isCombatDamage() && damage > 0) { + this.getEffects().setValue("damageAmount", damage); + return true; } - this.getEffects().setValue("damageAmount", event.getAmount()); - return true; + return false; } } diff --git a/Mage.Sets/src/mage/cards/w/WallOfSouls.java b/Mage.Sets/src/mage/cards/w/WallOfSouls.java index 6a063a058a3..1168be41b3f 100644 --- a/Mage.Sets/src/mage/cards/w/WallOfSouls.java +++ b/Mage.Sets/src/mage/cards/w/WallOfSouls.java @@ -1,6 +1,5 @@ package mage.cards.w; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; @@ -13,10 +12,12 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; import mage.game.Game; -import mage.game.events.DamagedEvent; +import mage.game.events.DamagedBatchForOnePermanentEvent; import mage.game.events.GameEvent; import mage.target.common.TargetOpponentOrPlaneswalker; +import java.util.UUID; + /** * * @author fireshoes @@ -66,13 +67,17 @@ class WallOfSoulsTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT; } @Override public boolean checkTrigger(GameEvent event, Game game) { - if (event.getTargetId().equals(this.sourceId) && ((DamagedEvent) event).isCombatDamage()) { - this.getEffects().setValue("damage", event.getAmount()); + + DamagedBatchForOnePermanentEvent dEvent = (DamagedBatchForOnePermanentEvent) event; + int damage = dEvent.getAmount(); + + if (event.getTargetId().equals(this.sourceId) && dEvent.isCombatDamage() && damage > 0) { + this.getEffects().setValue("damage", damage); return true; } return false; diff --git a/Mage.Sets/src/mage/cards/w/WantedGriffin.java b/Mage.Sets/src/mage/cards/w/WantedGriffin.java new file mode 100644 index 00000000000..f4fb6664561 --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WantedGriffin.java @@ -0,0 +1,42 @@ +package mage.cards.w; + +import mage.MageInt; +import mage.abilities.common.DiesSourceTriggeredAbility; +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.MercenaryToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class WantedGriffin extends CardImpl { + + public WantedGriffin(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); + + this.subtype.add(SubType.GRIFFIN); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When Wanted Griffin dies, create a 1/1 red Mercenary creature token with "{T}: Target creature you control gets +1/+0 until end of turn. Activate only as a sorcery." + this.addAbility(new DiesSourceTriggeredAbility(new CreateTokenEffect(new MercenaryToken()))); + } + + private WantedGriffin(final WantedGriffin card) { + super(card); + } + + @Override + public WantedGriffin copy() { + return new WantedGriffin(this); + } +} diff --git a/Mage.Sets/src/mage/cards/w/WarElemental.java b/Mage.Sets/src/mage/cards/w/WarElemental.java index bf78701cce7..29b0f742e79 100644 --- a/Mage.Sets/src/mage/cards/w/WarElemental.java +++ b/Mage.Sets/src/mage/cards/w/WarElemental.java @@ -70,12 +70,12 @@ class WarElementalTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PLAYER; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PLAYER; } @Override public boolean checkTrigger(GameEvent event, Game game) { - if (game.getOpponents(this.controllerId).contains(event.getPlayerId())) { + if (game.getOpponents(this.controllerId).contains(event.getTargetId())) { this.getEffects().get(0).setValue("damageAmount", event.getAmount()); return true; } diff --git a/Mage.Sets/src/mage/cards/w/WaytaTrainerProdigy.java b/Mage.Sets/src/mage/cards/w/WaytaTrainerProdigy.java index 14226112377..7101a6ad3f2 100644 --- a/Mage.Sets/src/mage/cards/w/WaytaTrainerProdigy.java +++ b/Mage.Sets/src/mage/cards/w/WaytaTrainerProdigy.java @@ -1,9 +1,5 @@ package mage.cards.w; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; -import java.util.Objects; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; @@ -14,13 +10,12 @@ import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.ReplacementEffectImpl; import mage.abilities.effects.common.FightTargetsEffect; import mage.abilities.effects.common.InfoEffect; -import mage.constants.*; import mage.abilities.keyword.HasteAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.constants.*; import mage.filter.StaticFilters; import mage.game.Game; -import mage.game.events.BatchGameEvent; import mage.game.events.GameEvent; import mage.game.events.NumberOfTriggersEvent; import mage.game.permanent.Permanent; @@ -29,6 +24,9 @@ import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetCreaturePermanent; import mage.util.CardUtil; +import java.util.Objects; +import java.util.UUID; + /** * @@ -158,35 +156,27 @@ class WaytaTrainerProdigyEffect extends ReplacementEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { - - NumberOfTriggersEvent numberOfTriggersEvent = (NumberOfTriggersEvent) event; - // Permanent whose ability is being triggered (and will be retriggered) - Permanent triggeredPermanent = game.getPermanentOrLKIBattlefield(numberOfTriggersEvent.getSourceId()); + Permanent triggeredPermanent = game.getPermanentOrLKIBattlefield(event.getSourceId()); if (triggeredPermanent == null || !triggeredPermanent.isControlledBy(source.getControllerId())) { return false; } - - GameEvent sourceEvent = numberOfTriggersEvent.getSourceEvent(); + GameEvent sourceEvent = ((NumberOfTriggersEvent) event).getSourceEvent(); if (sourceEvent == null) { return false; } - - if (!(sourceEvent.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_PERMANENTS - || sourceEvent.getType() == GameEvent.EventType.DAMAGED_PERMANENT)){ - return false; + switch (sourceEvent.getType()) { + case DAMAGED_PERMANENT: + case DAMAGED_BATCH_FOR_ONE_PERMANENT: + case DAMAGED_BATCH_FOR_PERMANENTS: + case DAMAGED_BATCH_FOR_ALL: + break; + default: + return false; } - // Get the one or more permanents damaged in this event - List damagedPermanents = new ArrayList<>(); - if (sourceEvent instanceof BatchGameEvent) { - damagedPermanents.addAll(((BatchGameEvent) sourceEvent).getTargets()); - } else { - damagedPermanents.add(sourceEvent.getTargetId()); - } - // If none of them are controlled by you, this ability doesn't apply. - return damagedPermanents + return CardUtil.getEventTargets(sourceEvent) .stream() .map(game::getPermanentOrLKIBattlefield) .filter(Objects::nonNull) diff --git a/Mage.Sets/src/mage/cards/w/WelcomeTo.java b/Mage.Sets/src/mage/cards/w/WelcomeTo.java index 0cefaca1fc2..ac854728ea1 100644 --- a/Mage.Sets/src/mage/cards/w/WelcomeTo.java +++ b/Mage.Sets/src/mage/cards/w/WelcomeTo.java @@ -17,13 +17,11 @@ import mage.cards.CardSetInfo; import mage.constants.*; import mage.filter.FilterPermanent; import mage.filter.predicate.Predicates; -import mage.filter.predicate.permanent.ControllerIdPredicate; import mage.game.Game; import mage.game.permanent.token.DinosaurToken; import mage.game.permanent.token.custom.CreatureToken; -import mage.players.Player; import mage.target.TargetPermanent; -import mage.target.targetadjustment.TargetAdjuster; +import mage.target.targetadjustment.EachOpponentPermanentTargetsAdjuster; import mage.target.targetpointer.EachTargetPointer; import mage.target.targetpointer.FixedTarget; @@ -36,9 +34,12 @@ import java.util.UUID; public final class WelcomeTo extends CardImpl { private static final FilterPermanent filter = new FilterPermanent("Walls"); + private static final FilterPermanent filterNoncreatureArtifact = new FilterPermanent("noncreature artifact"); static { filter.add(SubType.WALL.getPredicate()); + filterNoncreatureArtifact.add(Predicates.not(CardType.CREATURE.getPredicate())); + filterNoncreatureArtifact.add(CardType.ARTIFACT.getPredicate()); } // Based on Azusa's Many Journeys // Likeness of the Seeker, Vronos Masked Inquisitor, In the Darkness Bind Then, @@ -62,7 +63,7 @@ public final class WelcomeTo extends CardImpl { ).setText("For each opponent, up to one target noncreature artifact they control becomes " + "a 0/4 Wall artifact creature with defender for as long as you control this Saga.")); ability.getEffects().setTargetPointer(new EachTargetPointer()); - ability.setTargetAdjuster(WelcomeToAdjuster.instance); + ability.setTargetAdjuster(new EachOpponentPermanentTargetsAdjuster(new TargetPermanent(0, 1, filterNoncreatureArtifact))); }); // II -- Create a 3/3 green Dinosaur creature token with trample. It gains haste until end of turn. @@ -89,28 +90,6 @@ public final class WelcomeTo extends CardImpl { } } -// Based on Vronos, Masked Inquisitor -enum WelcomeToAdjuster implements TargetAdjuster { - instance; - - @Override - public void adjustTargets(Ability ability, Game game) { - ability.getTargets().clear(); - for (UUID opponentId : game.getOpponents(ability.getControllerId())) { - Player opponent = game.getPlayer(opponentId); - if (opponent == null) { - continue; - } - FilterPermanent filter = new FilterPermanent( - "noncreature artifact controlled by " + opponent.getLogName()); - filter.add(Predicates.not(CardType.CREATURE.getPredicate())); - filter.add(CardType.ARTIFACT.getPredicate()); - filter.add(new ControllerIdPredicate(opponentId)); - ability.addTarget(new TargetPermanent(0, 1, filter, false)); - } - } -} - // Based on Mordor on the March class WelcomeToEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/w/WellRested.java b/Mage.Sets/src/mage/cards/w/WellRested.java new file mode 100644 index 00000000000..614569015af --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WellRested.java @@ -0,0 +1,61 @@ +package mage.cards.w; + +import java.util.UUID; + +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.InspiredAbility; +import mage.constants.AttachmentType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.target.common.TargetCreaturePermanent; +import mage.abilities.effects.common.AttachEffect; +import mage.constants.Outcome; +import mage.target.TargetPermanent; +import mage.abilities.keyword.EnchantAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; + +/** + * @author Cguy7777 + */ +public final class WellRested extends CardImpl { + + public WellRested(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{G}"); + + this.subtype.add(SubType.AURA); + + // Enchant creature + TargetPermanent auraTarget = new TargetCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + this.addAbility(new EnchantAbility(auraTarget)); + + // Enchanted creature has "Whenever this creature becomes untapped, put two +1/+1 counters on it, + // then you gain 2 life and draw a card. This ability triggers only once each turn." + InspiredAbility gainedAbility = new InspiredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance(2)), false, false); + gainedAbility.addEffect(new GainLifeEffect(2).concatBy(", then")); + gainedAbility.addEffect(new DrawCardSourceControllerEffect(1).concatBy("and")); + gainedAbility.setTriggersOnceEachTurn(true); + + this.addAbility(new SimpleStaticAbility(new GainAbilityAttachedEffect(gainedAbility, AttachmentType.AURA) + .setText("Enchanted creature has \"Whenever this creature becomes untapped, " + + "put two +1/+1 counters on it, then you gain 2 life and draw a card. " + + "This ability triggers only once each turn.\""))); + } + + private WellRested(final WellRested card) { + super(card); + } + + @Override + public WellRested copy() { + return new WellRested(this); + } +} diff --git a/Mage.Sets/src/mage/cards/w/WildfireElemental.java b/Mage.Sets/src/mage/cards/w/WildfireElemental.java index c6080478dde..26d8c315f6d 100644 --- a/Mage.Sets/src/mage/cards/w/WildfireElemental.java +++ b/Mage.Sets/src/mage/cards/w/WildfireElemental.java @@ -10,7 +10,7 @@ import mage.constants.Duration; import mage.constants.SubType; import mage.constants.Zone; import mage.game.Game; -import mage.game.events.DamagedPlayerEvent; +import mage.game.events.DamagedBatchForOnePlayerEvent; import mage.game.events.GameEvent; import java.util.UUID; @@ -58,14 +58,13 @@ class WildfireElementalTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PLAYER; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PLAYER; } @Override public boolean checkTrigger(GameEvent event, Game game) { - DamagedPlayerEvent damageEvent = (DamagedPlayerEvent) event; - return !damageEvent.isCombatDamage() - && game.getOpponents(controllerId).contains(event.getTargetId()); + DamagedBatchForOnePlayerEvent dEvent = (DamagedBatchForOnePlayerEvent) event; + return !dEvent.isCombatDamage() && dEvent.getAmount() > 0 && game.getOpponents(controllerId).contains(dEvent.getTargetId()); } @Override diff --git a/Mage.Sets/src/mage/cards/w/WillingTestSubject.java b/Mage.Sets/src/mage/cards/w/WillingTestSubject.java index 1913a8e8334..4681754658d 100644 --- a/Mage.Sets/src/mage/cards/w/WillingTestSubject.java +++ b/Mage.Sets/src/mage/cards/w/WillingTestSubject.java @@ -80,7 +80,7 @@ class WillingTestSubjectTriggeredAbility extends TriggeredAbilityImpl { public boolean checkTrigger(GameEvent event, Game game) { DieRolledEvent drEvent = (DieRolledEvent) event; // silver border card must look for "result" instead "natural result" - return this.isControlledBy(event.getPlayerId()) && drEvent.getResult() >= 4; + return this.isControlledBy(event.getTargetId()) && drEvent.getResult() >= 4; } @Override diff --git a/Mage.Sets/src/mage/cards/w/WindswiftSlice.java b/Mage.Sets/src/mage/cards/w/WindswiftSlice.java index 78c0acc2603..bb74bc96156 100644 --- a/Mage.Sets/src/mage/cards/w/WindswiftSlice.java +++ b/Mage.Sets/src/mage/cards/w/WindswiftSlice.java @@ -76,7 +76,7 @@ class WindswiftSliceEffect extends OneShotEffect { int lethal = anotherPermanent.getLethalDamage(myPermanent.getId(), game); lethal = Math.min(lethal, power); - anotherPermanent.damage(lethal, myPermanent.getId(), source, game); + anotherPermanent.damage(power, myPermanent.getId(), source, game); if (lethal < power) { new ElfWarriorToken().putOntoBattlefield(power - lethal, game, source, source.getControllerId()); diff --git a/Mage.Sets/src/mage/cards/w/WorldwalkerHelm.java b/Mage.Sets/src/mage/cards/w/WorldwalkerHelm.java new file mode 100644 index 00000000000..d0610704bbb --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WorldwalkerHelm.java @@ -0,0 +1,110 @@ +package mage.cards.w; + +import mage.MageObject; +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.ManaCostsImpl; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.CreateTokenCopyTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledArtifactPermanent; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.game.Game; +import mage.game.events.CreateTokenEvent; +import mage.game.events.GameEvent; +import mage.game.permanent.token.MapToken; +import mage.target.TargetPermanent; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class WorldwalkerHelm extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledArtifactPermanent("artifact token you control"); + + static { + filter.add(TokenPredicate.TRUE); + } + + public WorldwalkerHelm(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{U}"); + + // If you would create one or more artifact tokens, instead create those tokens plus an additional Map token. + this.addAbility(new SimpleStaticAbility(new WorldwalkerHelmReplacementEffect())); + + // {1}{U}, {T}: Create a token that's a copy of target artifact token you control. + Ability ability = new SimpleActivatedAbility(new CreateTokenCopyTargetEffect(), new ManaCostsImpl<>("{1}{U}")); + ability.addCost(new TapSourceCost()); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + } + + private WorldwalkerHelm(final WorldwalkerHelm card) { + super(card); + } + + @Override + public WorldwalkerHelm copy() { + return new WorldwalkerHelm(this); + } +} + +class WorldwalkerHelmReplacementEffect extends ReplacementEffectImpl { + + WorldwalkerHelmReplacementEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit); + this.staticText = "if you would create one or more artifact tokens, instead create those tokens plus an additional Map token"; + } + + private WorldwalkerHelmReplacementEffect(final WorldwalkerHelmReplacementEffect effect) { + super(effect); + } + + @Override + public WorldwalkerHelmReplacementEffect copy() { + return new WorldwalkerHelmReplacementEffect(this); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.CREATE_TOKEN; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + return source.isControlledBy(event.getPlayerId()); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + if (!(event instanceof CreateTokenEvent)) { + return false; + } + CreateTokenEvent tokenEvent = (CreateTokenEvent) event; + if (tokenEvent + .getTokens() + .keySet() + .stream() + .noneMatch(MageObject::isArtifact)) { + return false; + } + MapToken mapToken = CardUtil + .castStream(tokenEvent.getTokens().keySet().stream(), MapToken.class) + .findFirst() + .orElseGet(MapToken::new); + tokenEvent + .getTokens() + .compute(mapToken, CardUtil::setOrIncrementValue); + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/w/WranglerOfTheDamned.java b/Mage.Sets/src/mage/cards/w/WranglerOfTheDamned.java new file mode 100644 index 00000000000..fa7e15c13e7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WranglerOfTheDamned.java @@ -0,0 +1,49 @@ +package mage.cards.w; + +import mage.MageInt; +import mage.abilities.common.BeginningOfEndStepTriggeredAbility; +import mage.abilities.condition.common.HaventCastSpellFromHandThisTurnCondition; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.FlashAbility; +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.game.permanent.token.Spirit22Token; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class WranglerOfTheDamned extends CardImpl { + + public WranglerOfTheDamned(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}{U}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(1); + this.toughness = new MageInt(4); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // At the beginning of your end step, if you haven't cast a spell from your hand this turn, create a 2/2 white Spirit creature token with flying. + this.addAbility(new BeginningOfEndStepTriggeredAbility( + Zone.BATTLEFIELD, new CreateTokenEffect(new Spirit22Token()), + TargetController.YOU, HaventCastSpellFromHandThisTurnCondition.instance, false + ).addHint(HaventCastSpellFromHandThisTurnCondition.hint)); + } + + private WranglerOfTheDamned(final WranglerOfTheDamned card) { + super(card); + } + + @Override + public WranglerOfTheDamned copy() { + return new WranglerOfTheDamned(this); + } +} diff --git a/Mage.Sets/src/mage/cards/w/WrathfulRaptors.java b/Mage.Sets/src/mage/cards/w/WrathfulRaptors.java index b52afcba949..f994d91f537 100644 --- a/Mage.Sets/src/mage/cards/w/WrathfulRaptors.java +++ b/Mage.Sets/src/mage/cards/w/WrathfulRaptors.java @@ -75,7 +75,7 @@ class WrathfulRaptorsTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT; } @Override diff --git a/Mage.Sets/src/mage/cards/w/WrathfulRedDragon.java b/Mage.Sets/src/mage/cards/w/WrathfulRedDragon.java index 3e2b9314554..ac1d42c5868 100644 --- a/Mage.Sets/src/mage/cards/w/WrathfulRedDragon.java +++ b/Mage.Sets/src/mage/cards/w/WrathfulRedDragon.java @@ -76,7 +76,7 @@ class WrathfulRedDragonTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT; } @Override diff --git a/Mage.Sets/src/mage/cards/w/WrexialTheRisenDeep.java b/Mage.Sets/src/mage/cards/w/WrexialTheRisenDeep.java index 20e474b4de9..5cfca700972 100644 --- a/Mage.Sets/src/mage/cards/w/WrexialTheRisenDeep.java +++ b/Mage.Sets/src/mage/cards/w/WrexialTheRisenDeep.java @@ -2,7 +2,7 @@ package mage.cards.w; import mage.MageInt; import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.effects.common.MayCastTargetThenExileEffect; +import mage.abilities.effects.common.MayCastTargetCardEffect; import mage.abilities.effects.common.replacement.ThatSpellGraveyardExileReplacementEffect; import mage.abilities.keyword.IslandwalkAbility; import mage.abilities.keyword.SwampwalkAbility; @@ -57,7 +57,7 @@ public final class WrexialTheRisenDeep extends CardImpl { class WrexialTheRisenDeepTriggeredAbility extends TriggeredAbilityImpl { WrexialTheRisenDeepTriggeredAbility() { - super(Zone.BATTLEFIELD, new MayCastTargetThenExileEffect(true) + super(Zone.BATTLEFIELD, new MayCastTargetCardEffect(CastManaAdjustment.WITHOUT_PAYING_MANA_COST, true) .setText("you may cast target instant or sorcery card from " + "that player's graveyard without paying its mana cost. " + ThatSpellGraveyardExileReplacementEffect.RULE_A), false); @@ -92,7 +92,7 @@ class WrexialTheRisenDeepTriggeredAbility extends TriggeredAbilityImpl { filter.add(Predicates.or( CardType.INSTANT.getPredicate(), CardType.SORCERY.getPredicate() - )); + )); Target target = new TargetCardInGraveyard(filter); this.getTargets().clear(); this.addTarget(target); diff --git a/Mage.Sets/src/mage/cards/w/WylieDukeAtiinHero.java b/Mage.Sets/src/mage/cards/w/WylieDukeAtiinHero.java new file mode 100644 index 00000000000..fff96762e43 --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WylieDukeAtiinHero.java @@ -0,0 +1,48 @@ +package mage.cards.w; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BecomesTappedSourceTriggeredAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.GainLifeEffect; +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 WylieDukeAtiinHero extends CardImpl { + + public WylieDukeAtiinHero(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.RANGER); + this.power = new MageInt(4); + this.toughness = new MageInt(2); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Whenever Wylie Duke, Atiin Hero becomes tapped, you gain 1 life and draw a card. + Ability ability = new BecomesTappedSourceTriggeredAbility(new GainLifeEffect(1)); + ability.addEffect(new DrawCardSourceControllerEffect(1).concatBy("and")); + this.addAbility(ability); + } + + private WylieDukeAtiinHero(final WylieDukeAtiinHero card) { + super(card); + } + + @Override + public WylieDukeAtiinHero copy() { + return new WylieDukeAtiinHero(this); + } +} diff --git a/Mage.Sets/src/mage/cards/x/XanatharGuildKingpin.java b/Mage.Sets/src/mage/cards/x/XanatharGuildKingpin.java index 3cae01e63d0..cc0445d5f83 100644 --- a/Mage.Sets/src/mage/cards/x/XanatharGuildKingpin.java +++ b/Mage.Sets/src/mage/cards/x/XanatharGuildKingpin.java @@ -3,13 +3,9 @@ package mage.cards.x; import mage.MageInt; import mage.MageObject; import mage.abilities.Ability; +import mage.abilities.SpellAbility; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; -import mage.abilities.effects.AsThoughEffectImpl; -import mage.abilities.effects.AsThoughManaEffect; -import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; -import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeTargetEffect; -import mage.abilities.effects.common.continuous.PlayTheTopCardTargetEffect; +import mage.abilities.effects.*; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -44,9 +40,9 @@ public final class XanatharGuildKingpin extends CardImpl { .setText("choose target opponent. Until end of turn, that player can't cast spells,"), TargetController.YOU, false ); - ability.addEffect(new LookAtTopCardOfLibraryAnyTimeTargetEffect(Duration.EndOfTurn) + ability.addEffect(new XanatharLookAtTopCardOfLibraryEffect() .setText(" you may look at the top card of their library any time,")); - ability.addEffect(new PlayTheTopCardTargetEffect() + ability.addEffect(new XanatharPlayFromTopOfTargetLibraryEffect() .setText(" you may play the top card of their library,")); ability.addEffect(new XanatharGuildKingpinSpendManaAsAnyColorOneShotEffect() .setText(" and you may spend mana as thought it were mana of any color to cast spells this way")); @@ -102,6 +98,90 @@ class XanatharGuildKingpinRuleModifyingEffect extends ContinuousRuleModifyingEff } } +class XanatharLookAtTopCardOfLibraryEffect extends ContinuousEffectImpl { + + XanatharLookAtTopCardOfLibraryEffect() { + super(Duration.EndOfTurn, Layer.PlayerEffects, SubLayer.NA, Outcome.Benefit); + } + + private XanatharLookAtTopCardOfLibraryEffect(final XanatharLookAtTopCardOfLibraryEffect effect) { + super(effect); + } + + @Override + public XanatharLookAtTopCardOfLibraryEffect copy() { + return new XanatharLookAtTopCardOfLibraryEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + if (game.inCheckPlayableState()) { // Ignored - see https://github.com/magefree/mage/issues/6994 + return false; + } + Player controller = game.getPlayer(source.getControllerId()); + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); + if (controller == null || opponent == null) { + return false; + } + if (!canLookAtNextTopLibraryCard(game)) { + return false; + } + Card topCard = opponent.getLibrary().getFromTop(game); + if (topCard == null) { + return false; + } + controller.lookAtCards("Top card of " + opponent.getName() + "'s library", topCard, game); + return true; + } + +} + +class XanatharPlayFromTopOfTargetLibraryEffect extends AsThoughEffectImpl { + + XanatharPlayFromTopOfTargetLibraryEffect() { + super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfTurn, Outcome.Benefit); + } + + private XanatharPlayFromTopOfTargetLibraryEffect(final XanatharPlayFromTopOfTargetLibraryEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public XanatharPlayFromTopOfTargetLibraryEffect copy() { + return new XanatharPlayFromTopOfTargetLibraryEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + throw new IllegalArgumentException("Wrong code usage: can't call applies method on empty affectedAbility"); + } + + @Override + public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) { + if (!(affectedAbility instanceof SpellAbility) || !playerId.equals(source.getControllerId())) { + return false; + } + SpellAbility spell = (SpellAbility) affectedAbility; + Card cardToCheck = spell.getCharacteristics(game); + if (spell.getManaCosts().isEmpty()) { + return false; + } + Player controller = game.getPlayer(source.getControllerId()); + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); + if (controller == null || opponent == null) { + return false; + } + // main card of spell must be on top of the opponent's library + Card topCard = opponent.getLibrary().getFromTop(game); + return topCard != null && topCard.getId().equals(cardToCheck.getMainCard().getId()); + } +} + class XanatharGuildKingpinSpendManaAsAnyColorOneShotEffect extends OneShotEffect { XanatharGuildKingpinSpendManaAsAnyColorOneShotEffect() { diff --git a/Mage.Sets/src/mage/cards/y/YumaProudProtector.java b/Mage.Sets/src/mage/cards/y/YumaProudProtector.java new file mode 100644 index 00000000000..6a8a96eab59 --- /dev/null +++ b/Mage.Sets/src/mage/cards/y/YumaProudProtector.java @@ -0,0 +1,72 @@ +package mage.cards.y; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility; +import mage.abilities.common.PutCardIntoGraveFromAnywhereAllTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +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.filter.FilterCard; +import mage.filter.StaticFilters; +import mage.game.permanent.token.PlantWarriorToken; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class YumaProudProtector extends CardImpl { + + private static final DynamicValue xValue = new CardsInControllerGraveyardCount(StaticFilters.FILTER_CARD_LAND, 1); + private static final Hint hint = new ValueHint("Lands in your graveyard", xValue); + + private static final FilterCard filter = new FilterCard("a Desert card"); + + static { + filter.add(SubType.DESERT.getPredicate()); + } + + public YumaProudProtector(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{R}{G}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.RANGER); + this.power = new MageInt(6); + this.toughness = new MageInt(6); + + // This spell costs {1} less to cast for each land card in your graveyard. + this.addAbility(new SimpleStaticAbility( + Zone.ALL, new SpellCostReductionForEachSourceEffect(1, xValue) + ).addHint(hint)); + + // Whenever Yuma, Proud Protector enters the battlefield or attacks, you may sacrifice a land. If you do, draw a card. + this.addAbility(new EntersBattlefieldOrAttacksSourceTriggeredAbility( + new DoIfCostPaid(new DrawCardSourceControllerEffect(1), new SacrificeTargetCost(StaticFilters.FILTER_CONTROLLED_LAND_SHORT_TEXT)) + )); + + // Whenever a Desert card is put into your graveyard from anywhere, create a 4/2 green Plant Warrior creature token with reach. + this.addAbility(new PutCardIntoGraveFromAnywhereAllTriggeredAbility( + new CreateTokenEffect(new PlantWarriorToken()), false, filter, TargetController.YOU + )); + } + + private YumaProudProtector(final YumaProudProtector card) { + super(card); + } + + @Override + public YumaProudProtector copy() { + return new YumaProudProtector(this); + } +} diff --git a/Mage.Sets/src/mage/cards/z/Zephyrim.java b/Mage.Sets/src/mage/cards/z/Zephyrim.java index 6977c85f8d9..2d374ed7ee8 100644 --- a/Mage.Sets/src/mage/cards/z/Zephyrim.java +++ b/Mage.Sets/src/mage/cards/z/Zephyrim.java @@ -1,6 +1,7 @@ package mage.cards.z; import mage.MageInt; +import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.MiracleAbility; import mage.abilities.keyword.SquadAbility; @@ -26,7 +27,7 @@ public final class Zephyrim extends CardImpl { this.toughness = new MageInt(3); // Squad {2} - this.addAbility(new SquadAbility()); + this.addAbility(new SquadAbility(new GenericManaCost(2))); // Flying this.addAbility(FlyingAbility.getInstance()); diff --git a/Mage.Sets/src/mage/cards/z/ZurgoAndOjutai.java b/Mage.Sets/src/mage/cards/z/ZurgoAndOjutai.java index a1d54af0cb7..e033dbe1a28 100644 --- a/Mage.Sets/src/mage/cards/z/ZurgoAndOjutai.java +++ b/Mage.Sets/src/mage/cards/z/ZurgoAndOjutai.java @@ -20,7 +20,7 @@ import mage.filter.predicate.Predicate; import mage.filter.predicate.Predicates; import mage.filter.predicate.permanent.PermanentIdPredicate; import mage.game.Game; -import mage.game.events.DamagedBatchEvent; +import mage.game.events.DamagedBatchAllEvent; import mage.game.events.DamagedEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; @@ -92,14 +92,12 @@ class ZurgoAndOjutaiTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_PLAYERS - || event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_PERMANENTS; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ALL; } @Override public boolean checkTrigger(GameEvent event, Game game) { - DamagedBatchEvent dEvent = (DamagedBatchEvent) event; - List permanents = dEvent + List permanents = ((DamagedBatchAllEvent) event) .getEvents() .stream() .filter(DamagedEvent::isCombatDamage) diff --git a/Mage.Sets/src/mage/sets/BreakingNews.java b/Mage.Sets/src/mage/sets/BreakingNews.java new file mode 100644 index 00000000000..cbc9709652f --- /dev/null +++ b/Mage.Sets/src/mage/sets/BreakingNews.java @@ -0,0 +1,105 @@ +package mage.sets; + +import mage.cards.ExpansionSet; +import mage.constants.Rarity; +import mage.constants.SetType; + +/** + * @author TheElk801 + */ +public final class BreakingNews extends ExpansionSet { + + private static final BreakingNews instance = new BreakingNews(); + + public static BreakingNews getInstance() { + return instance; + } + + private BreakingNews() { + super("Breaking News", "OTP", ExpansionSet.buildDate(2024, 4, 19), SetType.SUPPLEMENTAL); + this.hasBoosters = false; + this.hasBasicLands = false; + this.maxCardNumberInBooster = 65; + + cards.add(new SetCardInfo("Abrupt Decay", 34, Rarity.RARE, mage.cards.a.AbruptDecay.class)); + cards.add(new SetCardInfo("Anguished Unmaking", 35, Rarity.MYTHIC, mage.cards.a.AnguishedUnmaking.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Anguished Unmaking", 74, Rarity.MYTHIC, mage.cards.a.AnguishedUnmaking.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Archive Trap", 7, Rarity.RARE, mage.cards.a.ArchiveTrap.class)); + cards.add(new SetCardInfo("Archmage's Charm", 8, Rarity.RARE, mage.cards.a.ArchmagesCharm.class)); + cards.add(new SetCardInfo("Back for More", 36, Rarity.UNCOMMON, mage.cards.b.BackForMore.class)); + cards.add(new SetCardInfo("Bedevil", 37, Rarity.RARE, mage.cards.b.Bedevil.class)); + cards.add(new SetCardInfo("Buried in the Garden", 38, Rarity.UNCOMMON, mage.cards.b.BuriedInTheGarden.class)); + cards.add(new SetCardInfo("Clear Shot", 28, Rarity.UNCOMMON, mage.cards.c.ClearShot.class)); + cards.add(new SetCardInfo("Collective Defiance", 21, Rarity.RARE, mage.cards.c.CollectiveDefiance.class)); + cards.add(new SetCardInfo("Commandeer", 9, Rarity.RARE, mage.cards.c.Commandeer.class)); + cards.add(new SetCardInfo("Contagion Engine", 61, Rarity.MYTHIC, mage.cards.c.ContagionEngine.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Contagion Engine", 78, Rarity.MYTHIC, mage.cards.c.ContagionEngine.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Crackle with Power", 22, Rarity.MYTHIC, mage.cards.c.CrackleWithPower.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Crackle with Power", 71, Rarity.MYTHIC, mage.cards.c.CrackleWithPower.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Crime // Punishment", 39, Rarity.MYTHIC, mage.cards.c.CrimePunishment.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Crime // Punishment", 75, Rarity.MYTHIC, mage.cards.c.CrimePunishment.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Cruel Ultimatum", 40, Rarity.RARE, mage.cards.c.CruelUltimatum.class)); + cards.add(new SetCardInfo("Decimate", 41, Rarity.RARE, mage.cards.d.Decimate.class)); + cards.add(new SetCardInfo("Decisive Denial", 42, Rarity.UNCOMMON, mage.cards.d.DecisiveDenial.class)); + cards.add(new SetCardInfo("Detention Sphere", 43, Rarity.RARE, mage.cards.d.DetentionSphere.class)); + cards.add(new SetCardInfo("Dust Bowl", 65, Rarity.RARE, mage.cards.d.DustBowl.class)); + cards.add(new SetCardInfo("Electrodominance", 23, Rarity.RARE, mage.cards.e.Electrodominance.class)); + cards.add(new SetCardInfo("Endless Detour", 44, Rarity.RARE, mage.cards.e.EndlessDetour.class)); + cards.add(new SetCardInfo("Essence Capture", 10, Rarity.UNCOMMON, mage.cards.e.EssenceCapture.class)); + cards.add(new SetCardInfo("Fell the Mighty", 1, Rarity.RARE, mage.cards.f.FellTheMighty.class)); + cards.add(new SetCardInfo("Fierce Retribution", 2, Rarity.UNCOMMON, mage.cards.f.FierceRetribution.class)); + cards.add(new SetCardInfo("Fling", 24, Rarity.UNCOMMON, mage.cards.f.Fling.class)); + cards.add(new SetCardInfo("Force of Vigor", 29, Rarity.MYTHIC, mage.cards.f.ForceOfVigor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Force of Vigor", 73, Rarity.MYTHIC, mage.cards.f.ForceOfVigor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Fractured Identity", 45, Rarity.MYTHIC, mage.cards.f.FracturedIdentity.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Fractured Identity", 76, Rarity.MYTHIC, mage.cards.f.FracturedIdentity.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Grindstone", 62, Rarity.MYTHIC, mage.cards.g.Grindstone.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Grindstone", 79, Rarity.MYTHIC, mage.cards.g.Grindstone.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Heartless Pillage", 14, Rarity.UNCOMMON, mage.cards.h.HeartlessPillage.class)); + cards.add(new SetCardInfo("Hindering Light", 46, Rarity.UNCOMMON, mage.cards.h.HinderingLight.class)); + cards.add(new SetCardInfo("Humiliate", 47, Rarity.UNCOMMON, mage.cards.h.Humiliate.class)); + cards.add(new SetCardInfo("Hypothesizzle", 48, Rarity.UNCOMMON, mage.cards.h.Hypothesizzle.class)); + cards.add(new SetCardInfo("Imp's Mischief", 15, Rarity.RARE, mage.cards.i.ImpsMischief.class)); + cards.add(new SetCardInfo("Indomitable Creativity", 25, Rarity.MYTHIC, mage.cards.i.IndomitableCreativity.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Indomitable Creativity", 72, Rarity.MYTHIC, mage.cards.i.IndomitableCreativity.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ionize", 49, Rarity.RARE, mage.cards.i.Ionize.class)); + cards.add(new SetCardInfo("Journey to Nowhere", 3, Rarity.UNCOMMON, mage.cards.j.JourneyToNowhere.class)); + cards.add(new SetCardInfo("Leyline Binding", 4, Rarity.MYTHIC, mage.cards.l.LeylineBinding.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Leyline Binding", 66, Rarity.MYTHIC, mage.cards.l.LeylineBinding.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mana Drain", 11, Rarity.MYTHIC, mage.cards.m.ManaDrain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mana Drain", 67, Rarity.MYTHIC, mage.cards.m.ManaDrain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mindbreak Trap", 12, Rarity.MYTHIC, mage.cards.m.MindbreakTrap.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mindbreak Trap", 68, Rarity.MYTHIC, mage.cards.m.MindbreakTrap.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mindslaver", 63, Rarity.MYTHIC, mage.cards.m.Mindslaver.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mindslaver", 80, Rarity.MYTHIC, mage.cards.m.Mindslaver.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Murder", 16, Rarity.UNCOMMON, mage.cards.m.Murder.class)); + cards.add(new SetCardInfo("Oko, Thief of Crowns", 50, Rarity.MYTHIC, mage.cards.o.OkoThiefOfCrowns.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Oko, Thief of Crowns", 77, Rarity.MYTHIC, mage.cards.o.OkoThiefOfCrowns.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Outlaws' Merriment", 51, Rarity.RARE, mage.cards.o.OutlawsMerriment.class)); + cards.add(new SetCardInfo("Overwhelming Forces", 17, Rarity.MYTHIC, mage.cards.o.OverwhelmingForces.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Overwhelming Forces", 69, Rarity.MYTHIC, mage.cards.o.OverwhelmingForces.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Pariah", 5, Rarity.RARE, mage.cards.p.Pariah.class)); + cards.add(new SetCardInfo("Path to Exile", 6, Rarity.RARE, mage.cards.p.PathToExile.class)); + cards.add(new SetCardInfo("Pest Infestation", 30, Rarity.RARE, mage.cards.p.PestInfestation.class)); + cards.add(new SetCardInfo("Primal Command", 31, Rarity.RARE, mage.cards.p.PrimalCommand.class)); + cards.add(new SetCardInfo("Primal Might", 32, Rarity.RARE, mage.cards.p.PrimalMight.class)); + cards.add(new SetCardInfo("Reanimate", 18, Rarity.RARE, mage.cards.r.Reanimate.class)); + cards.add(new SetCardInfo("Repulse", 13, Rarity.UNCOMMON, mage.cards.r.Repulse.class)); + cards.add(new SetCardInfo("Ride Down", 52, Rarity.UNCOMMON, mage.cards.r.RideDown.class)); + cards.add(new SetCardInfo("Savage Smash", 53, Rarity.UNCOMMON, mage.cards.s.SavageSmash.class)); + cards.add(new SetCardInfo("Siphon Insight", 54, Rarity.RARE, mage.cards.s.SiphonInsight.class)); + cards.add(new SetCardInfo("Skewer the Critics", 26, Rarity.UNCOMMON, mage.cards.s.SkewerTheCritics.class)); + cards.add(new SetCardInfo("Skullcrack", 27, Rarity.RARE, mage.cards.s.Skullcrack.class)); + cards.add(new SetCardInfo("Surgical Extraction", 19, Rarity.RARE, mage.cards.s.SurgicalExtraction.class)); + cards.add(new SetCardInfo("Terminal Agony", 55, Rarity.UNCOMMON, mage.cards.t.TerminalAgony.class)); + cards.add(new SetCardInfo("Thornado", 33, Rarity.UNCOMMON, mage.cards.t.Thornado.class)); + cards.add(new SetCardInfo("Thoughtseize", 20, Rarity.MYTHIC, mage.cards.t.Thoughtseize.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Thoughtseize", 70, Rarity.MYTHIC, mage.cards.t.Thoughtseize.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tyrant's Scorn", 56, Rarity.UNCOMMON, mage.cards.t.TyrantsScorn.class)); + cards.add(new SetCardInfo("Unlicensed Hearse", 64, Rarity.RARE, mage.cards.u.UnlicensedHearse.class)); + cards.add(new SetCardInfo("Vanishing Verse", 57, Rarity.RARE, mage.cards.v.VanishingVerse.class)); + cards.add(new SetCardInfo("Villainous Wealth", 58, Rarity.RARE, mage.cards.v.VillainousWealth.class)); + cards.add(new SetCardInfo("Void Rend", 59, Rarity.RARE, mage.cards.v.VoidRend.class)); + cards.add(new SetCardInfo("Voidslime", 60, Rarity.RARE, mage.cards.v.Voidslime.class)); + } +} diff --git a/Mage.Sets/src/mage/sets/DoctorWho.java b/Mage.Sets/src/mage/sets/DoctorWho.java index 0e7a70792eb..1086951d4d1 100644 --- a/Mage.Sets/src/mage/sets/DoctorWho.java +++ b/Mage.Sets/src/mage/sets/DoctorWho.java @@ -24,6 +24,7 @@ public final class DoctorWho extends ExpansionSet { cards.add(new SetCardInfo("Adric, Mathematical Genius", 33, Rarity.RARE, mage.cards.a.AdricMathematicalGenius.class)); cards.add(new SetCardInfo("Alistair, the Brigadier", 112, Rarity.RARE, mage.cards.a.AlistairTheBrigadier.class)); cards.add(new SetCardInfo("All of History, All at Once", 34, Rarity.RARE, mage.cards.a.AllOfHistoryAllAtOnce.class)); + cards.add(new SetCardInfo("Amy Pond", 75, Rarity.RARE, mage.cards.a.AmyPond.class)); cards.add(new SetCardInfo("An Unearthly Child", 35, Rarity.RARE, mage.cards.a.AnUnearthlyChild.class)); cards.add(new SetCardInfo("Arcane Signet", 239, Rarity.COMMON, mage.cards.a.ArcaneSignet.class)); cards.add(new SetCardInfo("As Foretold", 214, Rarity.RARE, mage.cards.a.AsForetold.class)); @@ -35,6 +36,7 @@ public final class DoctorWho extends ExpansionSet { cards.add(new SetCardInfo("Beast Within", 228, Rarity.UNCOMMON, mage.cards.b.BeastWithin.class)); cards.add(new SetCardInfo("Become the Pilot", 37, Rarity.RARE, mage.cards.b.BecomeThePilot.class)); cards.add(new SetCardInfo("Bessie, the Doctor's Roadster", 171, Rarity.RARE, mage.cards.b.BessieTheDoctorsRoadster.class)); + cards.add(new SetCardInfo("Bigger on the Inside", 115, Rarity.UNCOMMON, mage.cards.b.BiggerOnTheInside.class)); cards.add(new SetCardInfo("Blasphemous Act", 224, Rarity.RARE, mage.cards.b.BlasphemousAct.class)); cards.add(new SetCardInfo("Canopy Vista", 258, Rarity.RARE, mage.cards.c.CanopyVista.class)); cards.add(new SetCardInfo("Canyon Slough", 259, Rarity.RARE, mage.cards.c.CanyonSlough.class)); @@ -81,6 +83,7 @@ public final class DoctorWho extends ExpansionSet { cards.add(new SetCardInfo("Duggan, Private Detective", 728, Rarity.RARE, mage.cards.d.DugganPrivateDetective.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Duggan, Private Detective", 1000, Rarity.RARE, mage.cards.d.DugganPrivateDetective.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Ecstatic Beauty", 83, Rarity.RARE, mage.cards.e.EcstaticBeauty.class)); + cards.add(new SetCardInfo("Everything Comes to Dust", 19, Rarity.RARE, mage.cards.e.EverythingComesToDust.class)); cards.add(new SetCardInfo("Evolving Wilds", 275, Rarity.COMMON, mage.cards.e.EvolvingWilds.class)); cards.add(new SetCardInfo("Exotic Orchard", 276, Rarity.RARE, mage.cards.e.ExoticOrchard.class)); cards.add(new SetCardInfo("Explore", 231, Rarity.COMMON, mage.cards.e.Explore.class)); @@ -166,11 +169,13 @@ public final class DoctorWho extends ExpansionSet { cards.add(new SetCardInfo("Reliquary Tower", 296, Rarity.UNCOMMON, mage.cards.r.ReliquaryTower.class)); cards.add(new SetCardInfo("Renegade Silent", 53, Rarity.UNCOMMON, mage.cards.r.RenegadeSilent.class)); cards.add(new SetCardInfo("Return to Dust", 211, Rarity.UNCOMMON, mage.cards.r.ReturnToDust.class)); + cards.add(new SetCardInfo("Reverse the Polarity", 54, Rarity.RARE, mage.cards.r.ReverseThePolarity.class)); cards.add(new SetCardInfo("River of Tears", 297, Rarity.RARE, mage.cards.r.RiverOfTears.class)); cards.add(new SetCardInfo("Rockfall Vale", 298, Rarity.RARE, mage.cards.r.RockfallVale.class)); cards.add(new SetCardInfo("Rogue's Passage", 299, Rarity.UNCOMMON, mage.cards.r.RoguesPassage.class)); cards.add(new SetCardInfo("Romana II", 27, Rarity.RARE, mage.cards.r.RomanaII.class)); cards.add(new SetCardInfo("Rootbound Crag", 300, Rarity.RARE, mage.cards.r.RootboundCrag.class)); + cards.add(new SetCardInfo("Rory Williams", 153, Rarity.RARE, mage.cards.r.RoryWilliams.class)); cards.add(new SetCardInfo("Rose Tyler", 5, Rarity.RARE, mage.cards.r.RoseTyler.class)); cards.add(new SetCardInfo("Rotating Fireplace", 183, Rarity.RARE, mage.cards.r.RotatingFireplace.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Rotating Fireplace", 461, Rarity.RARE, mage.cards.r.RotatingFireplace.class, NON_FULL_USE_VARIOUS)); @@ -192,6 +197,7 @@ public final class DoctorWho extends ExpansionSet { cards.add(new SetCardInfo("Sol Ring", 245, Rarity.UNCOMMON, mage.cards.s.SolRing.class)); cards.add(new SetCardInfo("Solemn Simulacrum", 246, Rarity.RARE, mage.cards.s.SolemnSimulacrum.class)); cards.add(new SetCardInfo("Sonic Screwdriver", 184, Rarity.UNCOMMON, mage.cards.s.SonicScrewdriver.class)); + cards.add(new SetCardInfo("Sontaran General", 96, Rarity.UNCOMMON, mage.cards.s.SontaranGeneral.class)); cards.add(new SetCardInfo("Star Whale", 55, Rarity.UNCOMMON, mage.cards.s.StarWhale.class)); cards.add(new SetCardInfo("Start the TARDIS", 56, Rarity.UNCOMMON, mage.cards.s.StartTheTARDIS.class)); cards.add(new SetCardInfo("Stormcarved Coast", 308, Rarity.RARE, mage.cards.s.StormcarvedCoast.class)); diff --git a/Mage.Sets/src/mage/sets/Fallout.java b/Mage.Sets/src/mage/sets/Fallout.java index 215c55f79f6..a1d9e23bef4 100644 --- a/Mage.Sets/src/mage/sets/Fallout.java +++ b/Mage.Sets/src/mage/sets/Fallout.java @@ -24,6 +24,7 @@ public final class Fallout extends ExpansionSet { cards.add(new SetCardInfo("Almost Perfect", 90, Rarity.RARE, mage.cards.a.AlmostPerfect.class)); cards.add(new SetCardInfo("Alpha Deathclaw", 91, Rarity.RARE, mage.cards.a.AlphaDeathclaw.class)); cards.add(new SetCardInfo("Anguished Unmaking", 209, Rarity.RARE, mage.cards.a.AnguishedUnmaking.class)); + cards.add(new SetCardInfo("Arcade Gannon", 92, Rarity.RARE, mage.cards.a.ArcadeGannon.class)); cards.add(new SetCardInfo("Arcane Signet", 224, Rarity.UNCOMMON, mage.cards.a.ArcaneSignet.class)); cards.add(new SetCardInfo("Armory Paladin", 93, Rarity.RARE, mage.cards.a.ArmoryPaladin.class)); cards.add(new SetCardInfo("Ash Barrens", 253, Rarity.COMMON, mage.cards.a.AshBarrens.class)); @@ -41,12 +42,19 @@ public final class Fallout extends ExpansionSet { cards.add(new SetCardInfo("Ayula, Queen Among Bears", 348, Rarity.RARE, mage.cards.a.AyulaQueenAmongBears.class)); cards.add(new SetCardInfo("Basilisk Collar", 225, Rarity.RARE, mage.cards.b.BasiliskCollar.class)); cards.add(new SetCardInfo("Bastion of Remembrance", 182, Rarity.UNCOMMON, mage.cards.b.BastionOfRemembrance.class)); + cards.add(new SetCardInfo("Battle of Hoover Dam", 11, Rarity.RARE, mage.cards.b.BattleOfHooverDam.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Battle of Hoover Dam", 364, Rarity.RARE, mage.cards.b.BattleOfHooverDam.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Battle of Hoover Dam", 539, Rarity.RARE, mage.cards.b.BattleOfHooverDam.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Battle of Hoover Dam", 892, Rarity.RARE, mage.cards.b.BattleOfHooverDam.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Behemoth Sledge", 211, Rarity.UNCOMMON, mage.cards.b.BehemothSledge.class)); + cards.add(new SetCardInfo("Behemoth of Vault 0", 127, Rarity.UNCOMMON, mage.cards.b.BehemothOfVault.class)); + cards.add(new SetCardInfo("Bighorner Rancher", 73, Rarity.UNCOMMON, mage.cards.b.BighornerRancher.class)); cards.add(new SetCardInfo("Biomass Mutation", 212, Rarity.RARE, mage.cards.b.BiomassMutation.class)); cards.add(new SetCardInfo("Black Market", 183, Rarity.RARE, mage.cards.b.BlackMarket.class)); cards.add(new SetCardInfo("Blasphemous Act", 188, Rarity.RARE, mage.cards.b.BlasphemousAct.class)); cards.add(new SetCardInfo("Bloodforged Battle-Axe", 226, Rarity.RARE, mage.cards.b.BloodforgedBattleAxe.class)); cards.add(new SetCardInfo("Boomer Scrapper", 95, Rarity.RARE, mage.cards.b.BoomerScrapper.class)); + cards.add(new SetCardInfo("Bottle-Cap Blast", 55, Rarity.UNCOMMON, mage.cards.b.BottleCapBlast.class)); cards.add(new SetCardInfo("Branching Evolution", 195, Rarity.RARE, mage.cards.b.BranchingEvolution.class)); cards.add(new SetCardInfo("Brass Knuckles", 227, Rarity.UNCOMMON, mage.cards.b.BrassKnuckles.class)); cards.add(new SetCardInfo("Break Down", 74, Rarity.UNCOMMON, mage.cards.b.BreakDown.class)); @@ -55,6 +63,7 @@ public final class Fallout extends ExpansionSet { cards.add(new SetCardInfo("Brotherhood Scribe", 365, Rarity.RARE, mage.cards.b.BrotherhoodScribe.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Brotherhood Scribe", 541, Rarity.RARE, mage.cards.b.BrotherhoodScribe.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Brotherhood Scribe", 893, Rarity.RARE, mage.cards.b.BrotherhoodScribe.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Brotherhood Vertibird", 128, Rarity.RARE, mage.cards.b.BrotherhoodVertibird.class)); cards.add(new SetCardInfo("Buried Ruin", 254, Rarity.UNCOMMON, mage.cards.b.BuriedRuin.class)); cards.add(new SetCardInfo("Caesar, Legion's Emperor", 1, Rarity.MYTHIC, mage.cards.c.CaesarLegionsEmperor.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Caesar, Legion's Emperor", 339, Rarity.MYTHIC, mage.cards.c.CaesarLegionsEmperor.class, NON_FULL_USE_VARIOUS)); @@ -65,6 +74,7 @@ public final class Fallout extends ExpansionSet { cards.add(new SetCardInfo("Canyon Slough", 256, Rarity.RARE, mage.cards.c.CanyonSlough.class)); cards.add(new SetCardInfo("Captain of the Watch", 157, Rarity.RARE, mage.cards.c.CaptainOfTheWatch.class)); cards.add(new SetCardInfo("Casualties of War", 213, Rarity.RARE, mage.cards.c.CasualtiesOfWar.class)); + cards.add(new SetCardInfo("Cathedral Acolyte", 75, Rarity.UNCOMMON, mage.cards.c.CathedralAcolyte.class)); cards.add(new SetCardInfo("Champion's Helm", 228, Rarity.RARE, mage.cards.c.ChampionsHelm.class)); cards.add(new SetCardInfo("Chaos Warp", 189, Rarity.RARE, mage.cards.c.ChaosWarp.class)); cards.add(new SetCardInfo("Cinder Glade", 257, Rarity.RARE, mage.cards.c.CinderGlade.class)); @@ -89,6 +99,11 @@ public final class Fallout extends ExpansionSet { cards.add(new SetCardInfo("Dr. Madison Li", 3, Rarity.MYTHIC, mage.cards.d.DrMadisonLi.class)); cards.add(new SetCardInfo("Dragonskull Summit", 261, Rarity.RARE, mage.cards.d.DragonskullSummit.class)); cards.add(new SetCardInfo("Drowned Catacomb", 262, Rarity.RARE, mage.cards.d.DrownedCatacomb.class)); + cards.add(new SetCardInfo("Elder Owyn Lyons", 103, Rarity.UNCOMMON, mage.cards.e.ElderOwynLyons.class)); + cards.add(new SetCardInfo("Electrosiphon", 104, Rarity.RARE, mage.cards.e.Electrosiphon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Electrosiphon", 414, Rarity.RARE, mage.cards.e.Electrosiphon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Electrosiphon", 632, Rarity.RARE, mage.cards.e.Electrosiphon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Electrosiphon", 942, Rarity.RARE, mage.cards.e.Electrosiphon.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Entrapment Maneuver", 160, Rarity.RARE, mage.cards.e.EntrapmentManeuver.class)); cards.add(new SetCardInfo("Everflowing Chalice", 230, Rarity.UNCOMMON, mage.cards.e.EverflowingChalice.class)); cards.add(new SetCardInfo("Evolving Wilds", 263, Rarity.COMMON, mage.cards.e.EvolvingWilds.class)); @@ -113,6 +128,7 @@ public final class Fallout extends ExpansionSet { cards.add(new SetCardInfo("Glimmer of Genius", 176, Rarity.UNCOMMON, mage.cards.g.GlimmerOfGenius.class)); cards.add(new SetCardInfo("Grave Titan", 346, Rarity.MYTHIC, mage.cards.g.GraveTitan.class)); cards.add(new SetCardInfo("Guardian Project", 199, Rarity.RARE, mage.cards.g.GuardianProject.class)); + cards.add(new SetCardInfo("Gunner Conscript", 77, Rarity.UNCOMMON, mage.cards.g.GunnerConscript.class)); cards.add(new SetCardInfo("Hancock, Ghoulish Mayor", 45, Rarity.RARE, mage.cards.h.HancockGhoulishMayor.class)); cards.add(new SetCardInfo("Hardened Scales", 200, Rarity.RARE, mage.cards.h.HardenedScales.class)); cards.add(new SetCardInfo("Harmonize", 201, Rarity.UNCOMMON, mage.cards.h.Harmonize.class)); @@ -122,6 +138,7 @@ public final class Fallout extends ExpansionSet { cards.add(new SetCardInfo("Hornet Queen", 350, Rarity.RARE, mage.cards.h.HornetQueen.class)); cards.add(new SetCardInfo("Hour of Reckoning", 161, Rarity.RARE, mage.cards.h.HourOfReckoning.class)); cards.add(new SetCardInfo("Hullbreaker Horror", 344, Rarity.RARE, mage.cards.h.HullbreakerHorror.class)); + cards.add(new SetCardInfo("Ian the Reckless", 59, Rarity.UNCOMMON, mage.cards.i.IanTheReckless.class)); cards.add(new SetCardInfo("Impassioned Orator", 162, Rarity.COMMON, mage.cards.i.ImpassionedOrator.class)); cards.add(new SetCardInfo("Inexorable Tide", 177, Rarity.RARE, mage.cards.i.InexorableTide.class)); cards.add(new SetCardInfo("Inspiring Call", 203, Rarity.UNCOMMON, mage.cards.i.InspiringCall.class)); @@ -137,9 +154,14 @@ public final class Fallout extends ExpansionSet { cards.add(new SetCardInfo("Kellogg, Dangerous Mind", 415, Rarity.RARE, mage.cards.k.KelloggDangerousMind.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Kellogg, Dangerous Mind", 634, Rarity.RARE, mage.cards.k.KelloggDangerousMind.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Kellogg, Dangerous Mind", 943, Rarity.RARE, mage.cards.k.KelloggDangerousMind.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Legate Lanius, Caesar's Ace", 107, Rarity.UNCOMMON, mage.cards.l.LegateLaniusCaesarsAce.class)); cards.add(new SetCardInfo("Lethal Scheme", 185, Rarity.RARE, mage.cards.l.LethalScheme.class)); cards.add(new SetCardInfo("Liberty Prime, Recharged", 5, Rarity.MYTHIC, mage.cards.l.LibertyPrimeRecharged.class)); cards.add(new SetCardInfo("Lightning Greaves", 233, Rarity.UNCOMMON, mage.cards.l.LightningGreaves.class)); + cards.add(new SetCardInfo("Lily Bowen, Raging Grandma", 79, Rarity.RARE, mage.cards.l.LilyBowenRagingGrandma.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lily Bowen, Raging Grandma", 399, Rarity.RARE, mage.cards.l.LilyBowenRagingGrandma.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lily Bowen, Raging Grandma", 607, Rarity.RARE, mage.cards.l.LilyBowenRagingGrandma.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lily Bowen, Raging Grandma", 927, Rarity.RARE, mage.cards.l.LilyBowenRagingGrandma.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Lord of the Undead", 345, Rarity.RARE, mage.cards.l.LordOfTheUndead.class)); cards.add(new SetCardInfo("Loyal Apprentice", 190, Rarity.UNCOMMON, mage.cards.l.LoyalApprentice.class)); cards.add(new SetCardInfo("MacCready, Lamplight Mayor", 108, Rarity.RARE, mage.cards.m.MacCreadyLamplightMayor.class, NON_FULL_USE_VARIOUS)); @@ -168,15 +190,32 @@ public final class Fallout extends ExpansionSet { cards.add(new SetCardInfo("Nerd Rage", 34, Rarity.UNCOMMON, mage.cards.n.NerdRage.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Nerd Rage", 562, Rarity.UNCOMMON, mage.cards.n.NerdRage.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Nesting Grounds", 276, Rarity.RARE, mage.cards.n.NestingGrounds.class)); + cards.add(new SetCardInfo("Nick Valentine, Private Eye", 35, Rarity.RARE, mage.cards.n.NickValentinePrivateEye.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Nick Valentine, Private Eye", 378, Rarity.RARE, mage.cards.n.NickValentinePrivateEye.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Nick Valentine, Private Eye", 563, Rarity.RARE, mage.cards.n.NickValentinePrivateEye.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Nick Valentine, Private Eye", 906, Rarity.RARE, mage.cards.n.NickValentinePrivateEye.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Nomad Outpost", 277, Rarity.UNCOMMON, mage.cards.n.NomadOutpost.class)); cards.add(new SetCardInfo("Nuka-Cola Vending Machine", 137, Rarity.UNCOMMON, mage.cards.n.NukaColaVendingMachine.class)); cards.add(new SetCardInfo("One with the Machine", 179, Rarity.RARE, mage.cards.o.OneWithTheMachine.class)); cards.add(new SetCardInfo("Open the Vaults", 168, Rarity.RARE, mage.cards.o.OpenTheVaults.class)); cards.add(new SetCardInfo("Opulent Palace", 278, Rarity.UNCOMMON, mage.cards.o.OpulentPalace.class)); cards.add(new SetCardInfo("Overflowing Basin", 152, Rarity.RARE, mage.cards.o.OverflowingBasin.class)); + cards.add(new SetCardInfo("Overseer of Vault 76", 19, Rarity.RARE, mage.cards.o.OverseerOfVault76.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Overseer of Vault 76", 368, Rarity.RARE, mage.cards.o.OverseerOfVault76.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Overseer of Vault 76", 547, Rarity.RARE, mage.cards.o.OverseerOfVault76.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Overseer of Vault 76", 896, Rarity.RARE, mage.cards.o.OverseerOfVault76.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Paladin Danse, Steel Maverick", 20, Rarity.UNCOMMON, mage.cards.p.PaladinDanseSteelMaverick.class)); cards.add(new SetCardInfo("Panharmonicon", 237, Rarity.RARE, mage.cards.p.Panharmonicon.class)); cards.add(new SetCardInfo("Path of Ancestry", 279, Rarity.COMMON, mage.cards.p.PathOfAncestry.class)); cards.add(new SetCardInfo("Path to Exile", 169, Rarity.UNCOMMON, mage.cards.p.PathToExile.class)); + cards.add(new SetCardInfo("Pip-Boy 3000", 140, Rarity.RARE, mage.cards.p.PipBoy3000.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Pip-Boy 3000", 435, Rarity.RARE, mage.cards.p.PipBoy3000.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Pip-Boy 3000", 668, Rarity.RARE, mage.cards.p.PipBoy3000.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Pip-Boy 3000", 963, Rarity.RARE, mage.cards.p.PipBoy3000.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Piper Wright, Publick Reporter", 36, Rarity.RARE, mage.cards.p.PiperWrightPublickReporter.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Piper Wright, Publick Reporter", 379, Rarity.RARE, mage.cards.p.PiperWrightPublickReporter.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Piper Wright, Publick Reporter", 564, Rarity.RARE, mage.cards.p.PiperWrightPublickReporter.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Piper Wright, Publick Reporter", 907, Rarity.RARE, mage.cards.p.PiperWrightPublickReporter.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Pitiless Plunderer", 187, Rarity.UNCOMMON, mage.cards.p.PitilessPlunderer.class)); cards.add(new SetCardInfo("Plains", 317, Rarity.LAND, mage.cards.basiclands.Plains.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Powder Ganger", 65, Rarity.RARE, mage.cards.p.PowderGanger.class)); @@ -193,13 +232,20 @@ public final class Fallout extends ExpansionSet { cards.add(new SetCardInfo("Red Death, Shipwrecker", 644, Rarity.RARE, mage.cards.r.RedDeathShipwrecker.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Red Death, Shipwrecker", 954, Rarity.RARE, mage.cards.r.RedDeathShipwrecker.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Roadside Reliquary", 282, Rarity.UNCOMMON, mage.cards.r.RoadsideReliquary.class)); + cards.add(new SetCardInfo("Robobrain War Mind", 38, Rarity.UNCOMMON, mage.cards.r.RobobrainWarMind.class)); cards.add(new SetCardInfo("Rogue's Passage", 283, Rarity.UNCOMMON, mage.cards.r.RoguesPassage.class)); cards.add(new SetCardInfo("Rootbound Crag", 284, Rarity.RARE, mage.cards.r.RootboundCrag.class)); cards.add(new SetCardInfo("Ruinous Ultimatum", 220, Rarity.RARE, mage.cards.r.RuinousUltimatum.class)); cards.add(new SetCardInfo("Rustvale Bridge", 285, Rarity.COMMON, mage.cards.r.RustvaleBridge.class)); + cards.add(new SetCardInfo("Ruthless Radrat", 48, Rarity.UNCOMMON, mage.cards.r.RuthlessRadrat.class)); cards.add(new SetCardInfo("Scattered Groves", 286, Rarity.RARE, mage.cards.s.ScatteredGroves.class)); cards.add(new SetCardInfo("Scavenger Grounds", 287, Rarity.RARE, mage.cards.s.ScavengerGrounds.class)); cards.add(new SetCardInfo("Secure the Wastes", 171, Rarity.RARE, mage.cards.s.SecureTheWastes.class)); + cards.add(new SetCardInfo("Securitron Squadron", 23, Rarity.RARE, mage.cards.s.SecuritronSquadron.class)); + cards.add(new SetCardInfo("Sentry Bot", 24, Rarity.RARE, mage.cards.s.SentryBot.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sentry Bot", 371, Rarity.RARE, mage.cards.s.SentryBot.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sentry Bot", 552, Rarity.RARE, mage.cards.s.SentryBot.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sentry Bot", 899, Rarity.RARE, mage.cards.s.SentryBot.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Shadowblood Ridge", 288, Rarity.RARE, mage.cards.s.ShadowbloodRidge.class)); cards.add(new SetCardInfo("Sheltered Thicket", 289, Rarity.RARE, mage.cards.s.ShelteredThicket.class)); cards.add(new SetCardInfo("Sierra, Nuka's Biggest Fan", 25, Rarity.RARE, mage.cards.s.SierraNukasBiggestFan.class, NON_FULL_USE_VARIOUS)); @@ -228,6 +274,7 @@ public final class Fallout extends ExpansionSet { cards.add(new SetCardInfo("Swamp", 321, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Swiftfoot Boots", 242, Rarity.UNCOMMON, mage.cards.s.SwiftfootBoots.class)); cards.add(new SetCardInfo("Swords to Plowshares", 173, Rarity.UNCOMMON, mage.cards.s.SwordsToPlowshares.class)); + cards.add(new SetCardInfo("T-45 Power Armor", 145, Rarity.RARE, mage.cards.t.T45PowerArmor.class)); cards.add(new SetCardInfo("Tainted Field", 298, Rarity.UNCOMMON, mage.cards.t.TaintedField.class)); cards.add(new SetCardInfo("Tainted Isle", 299, Rarity.UNCOMMON, mage.cards.t.TaintedIsle.class)); cards.add(new SetCardInfo("Tainted Peak", 300, Rarity.UNCOMMON, mage.cards.t.TaintedPeak.class)); @@ -262,6 +309,8 @@ public final class Fallout extends ExpansionSet { cards.add(new SetCardInfo("Valorous Stance", 174, Rarity.UNCOMMON, mage.cards.v.ValorousStance.class)); cards.add(new SetCardInfo("Vandalblast", 355, Rarity.UNCOMMON, mage.cards.v.Vandalblast.class)); cards.add(new SetCardInfo("Vault 101: Birthday Party", 28, Rarity.RARE, mage.cards.v.Vault101BirthdayParty.class)); + cards.add(new SetCardInfo("Vault 75: Middle School", 27, Rarity.RARE, mage.cards.v.Vault75MiddleSchool.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vault 75: Middle School", 555, Rarity.RARE, mage.cards.v.Vault75MiddleSchool.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Vigor", 347, Rarity.RARE, mage.cards.v.Vigor.class)); cards.add(new SetCardInfo("Viridescent Bog", 154, Rarity.RARE, mage.cards.v.ViridescentBog.class)); cards.add(new SetCardInfo("Wake the Past", 221, Rarity.RARE, mage.cards.w.WakeThePast.class)); @@ -271,6 +320,8 @@ public final class Fallout extends ExpansionSet { cards.add(new SetCardInfo("Watchful Radstag", 87, Rarity.RARE, mage.cards.w.WatchfulRadstag.class)); cards.add(new SetCardInfo("Wayfarer's Bauble", 252, Rarity.COMMON, mage.cards.w.WayfarersBauble.class)); cards.add(new SetCardInfo("Wear // Tear", 222, Rarity.UNCOMMON, mage.cards.w.WearTear.class)); + cards.add(new SetCardInfo("Well Rested", 88, Rarity.UNCOMMON, mage.cards.w.WellRested.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Well Rested", 616, Rarity.UNCOMMON, mage.cards.w.WellRested.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Whirler Rogue", 181, Rarity.UNCOMMON, mage.cards.w.WhirlerRogue.class)); cards.add(new SetCardInfo("Wild Growth", 208, Rarity.COMMON, mage.cards.w.WildGrowth.class)); cards.add(new SetCardInfo("Windbrisk Heights", 315, Rarity.RARE, mage.cards.w.WindbriskHeights.class)); diff --git a/Mage.Sets/src/mage/sets/MarchOfTheMachineTheAftermath.java b/Mage.Sets/src/mage/sets/MarchOfTheMachineTheAftermath.java index b26f67945c1..3a5121910c7 100644 --- a/Mage.Sets/src/mage/sets/MarchOfTheMachineTheAftermath.java +++ b/Mage.Sets/src/mage/sets/MarchOfTheMachineTheAftermath.java @@ -150,6 +150,7 @@ public final class MarchOfTheMachineTheAftermath extends ExpansionSet { cards.add(new SetCardInfo("Niv-Mizzet, Supreme", 219, Rarity.RARE, mage.cards.n.NivMizzetSupreme.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Niv-Mizzet, Supreme", 40, Rarity.RARE, mage.cards.n.NivMizzetSupreme.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Niv-Mizzet, Supreme", 90, Rarity.RARE, mage.cards.n.NivMizzetSupreme.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ob Nixilis, Captive Kingpin", 41, Rarity.MYTHIC, mage.cards.o.ObNixilisCaptiveKingpin.class)); cards.add(new SetCardInfo("Open the Way", 123, Rarity.RARE, mage.cards.o.OpenTheWay.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Open the Way", 163, Rarity.RARE, mage.cards.o.OpenTheWay.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Open the Way", 23, Rarity.RARE, mage.cards.o.OpenTheWay.class, NON_FULL_USE_VARIOUS)); diff --git a/Mage.Sets/src/mage/sets/ModernHorizons3.java b/Mage.Sets/src/mage/sets/ModernHorizons3.java index 505765bde27..a8249702e74 100644 --- a/Mage.Sets/src/mage/sets/ModernHorizons3.java +++ b/Mage.Sets/src/mage/sets/ModernHorizons3.java @@ -21,6 +21,8 @@ public final class ModernHorizons3 extends ExpansionSet { this.hasBasicLands = true; this.hasBoosters = false; // temporary + cards.add(new SetCardInfo("Ajani, Nacatl Avenger", 237, Rarity.MYTHIC, mage.cards.a.AjaniNacatlAvenger.class)); + cards.add(new SetCardInfo("Ajani, Nacatl Pariah", 237, Rarity.MYTHIC, mage.cards.a.AjaniNacatlPariah.class)); cards.add(new SetCardInfo("Bloodstained Mire", 216, Rarity.RARE, mage.cards.b.BloodstainedMire.class)); cards.add(new SetCardInfo("Flare of Cultivation", 154, Rarity.RARE, mage.cards.f.FlareOfCultivation.class)); cards.add(new SetCardInfo("Flooded Strand", 220, Rarity.RARE, mage.cards.f.FloodedStrand.class)); diff --git a/Mage.Sets/src/mage/sets/MurdersAtKarlovManor.java b/Mage.Sets/src/mage/sets/MurdersAtKarlovManor.java index cb08d1c1e74..d687d92e176 100644 --- a/Mage.Sets/src/mage/sets/MurdersAtKarlovManor.java +++ b/Mage.Sets/src/mage/sets/MurdersAtKarlovManor.java @@ -25,16 +25,20 @@ public final class MurdersAtKarlovManor extends ExpansionSet { cards.add(new SetCardInfo("Absolving Lammasu", 2, Rarity.UNCOMMON, mage.cards.a.AbsolvingLammasu.class)); cards.add(new SetCardInfo("Aftermath Analyst", 148, Rarity.UNCOMMON, mage.cards.a.AftermathAnalyst.class)); cards.add(new SetCardInfo("Agency Coroner", 75, Rarity.COMMON, mage.cards.a.AgencyCoroner.class)); + cards.add(new SetCardInfo("Agency Outfitter", 38, Rarity.UNCOMMON, mage.cards.a.AgencyOutfitter.class)); cards.add(new SetCardInfo("Agrus Kos, Spirit of Justice", 184, Rarity.MYTHIC, mage.cards.a.AgrusKosSpiritOfJustice.class)); cards.add(new SetCardInfo("Airtight Alibi", 149, Rarity.COMMON, mage.cards.a.AirtightAlibi.class)); cards.add(new SetCardInfo("Alley Assailant", 76, Rarity.COMMON, mage.cards.a.AlleyAssailant.class)); cards.add(new SetCardInfo("Alquist Proft, Master Sleuth", 185, Rarity.MYTHIC, mage.cards.a.AlquistProftMasterSleuth.class)); cards.add(new SetCardInfo("Analyze the Pollen", 150, Rarity.RARE, mage.cards.a.AnalyzeThePollen.class)); + cards.add(new SetCardInfo("Anzrag's Rampage", 111, Rarity.RARE, mage.cards.a.AnzragsRampage.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Anzrag's Rampage", 303, Rarity.RARE, mage.cards.a.AnzragsRampage.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Anzrag, the Quake-Mole", 186, Rarity.MYTHIC, mage.cards.a.AnzragTheQuakeMole.class)); cards.add(new SetCardInfo("Archdruid's Charm", 151, Rarity.RARE, mage.cards.a.ArchdruidsCharm.class)); cards.add(new SetCardInfo("Assassin's Trophy", 187, Rarity.RARE, mage.cards.a.AssassinsTrophy.class)); cards.add(new SetCardInfo("Assemble the Players", 3, Rarity.RARE, mage.cards.a.AssembleThePlayers.class)); cards.add(new SetCardInfo("Audience with Trostani", 152, Rarity.RARE, mage.cards.a.AudienceWithTrostani.class)); + cards.add(new SetCardInfo("Aurelia's Vindicator", 4, Rarity.MYTHIC, mage.cards.a.AureliasVindicator.class)); cards.add(new SetCardInfo("Aurelia, the Law Above", 188, Rarity.RARE, mage.cards.a.AureliaTheLawAbove.class)); cards.add(new SetCardInfo("Auspicious Arrival", 5, Rarity.COMMON, mage.cards.a.AuspiciousArrival.class)); cards.add(new SetCardInfo("Axebane Ferox", 153, Rarity.RARE, mage.cards.a.AxebaneFerox.class)); @@ -42,13 +46,19 @@ public final class MurdersAtKarlovManor extends ExpansionSet { cards.add(new SetCardInfo("Basilica Stalker", 78, Rarity.COMMON, mage.cards.b.BasilicaStalker.class)); cards.add(new SetCardInfo("Behind the Mask", 39, Rarity.COMMON, mage.cards.b.BehindTheMask.class)); cards.add(new SetCardInfo("Benthic Criminologists", 40, Rarity.COMMON, mage.cards.b.BenthicCriminologists.class)); + cards.add(new SetCardInfo("Bite Down on Crime", 154, Rarity.COMMON, mage.cards.b.BiteDownOnCrime.class)); cards.add(new SetCardInfo("Blood Spatter Analysis", 189, Rarity.RARE, mage.cards.b.BloodSpatterAnalysis.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Blood Spatter Analysis", 413, Rarity.RARE, mage.cards.b.BloodSpatterAnalysis.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Bolrac-Clan Basher", 112, Rarity.UNCOMMON, mage.cards.b.BolracClanBasher.class)); cards.add(new SetCardInfo("Branch of Vitu-Ghazi", 258, Rarity.UNCOMMON, mage.cards.b.BranchOfVituGhazi.class)); + cards.add(new SetCardInfo("Break Out", 190, Rarity.UNCOMMON, mage.cards.b.BreakOut.class)); + cards.add(new SetCardInfo("Bubble Smuggler", 41, Rarity.COMMON, mage.cards.b.BubbleSmuggler.class)); cards.add(new SetCardInfo("Burden of Proof", 42, Rarity.UNCOMMON, mage.cards.b.BurdenOfProof.class)); cards.add(new SetCardInfo("Buried in the Garden", 191, Rarity.UNCOMMON, mage.cards.b.BuriedInTheGarden.class)); + cards.add(new SetCardInfo("Call a Surprise Witness", 6, Rarity.UNCOMMON, mage.cards.c.CallASurpriseWitness.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Call a Surprise Witness", 289, Rarity.UNCOMMON, mage.cards.c.CallASurpriseWitness.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Candlestick", 43, Rarity.UNCOMMON, mage.cards.c.Candlestick.class)); + cards.add(new SetCardInfo("Case File Auditor", 7, Rarity.UNCOMMON, mage.cards.c.CaseFileAuditor.class)); cards.add(new SetCardInfo("Case of the Burning Masks", 113, Rarity.UNCOMMON, mage.cards.c.CaseOfTheBurningMasks.class)); cards.add(new SetCardInfo("Case of the Crimson Pulse", 114, Rarity.RARE, mage.cards.c.CaseOfTheCrimsonPulse.class)); cards.add(new SetCardInfo("Case of the Filched Falcon", 44, Rarity.UNCOMMON, mage.cards.c.CaseOfTheFilchedFalcon.class)); @@ -73,10 +83,12 @@ public final class MurdersAtKarlovManor extends ExpansionSet { cards.add(new SetCardInfo("Concealed Weapon", 117, Rarity.UNCOMMON, mage.cards.c.ConcealedWeapon.class)); cards.add(new SetCardInfo("Connecting the Dots", 118, Rarity.RARE, mage.cards.c.ConnectingTheDots.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Connecting the Dots", 403, Rarity.RARE, mage.cards.c.ConnectingTheDots.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Conspiracy Unraveler", 47, Rarity.MYTHIC, mage.cards.c.ConspiracyUnraveler.class)); cards.add(new SetCardInfo("Convenient Target", 119, Rarity.UNCOMMON, mage.cards.c.ConvenientTarget.class)); cards.add(new SetCardInfo("Cornered Crook", 120, Rarity.UNCOMMON, mage.cards.c.CorneredCrook.class)); cards.add(new SetCardInfo("Crime Novelist", 121, Rarity.UNCOMMON, mage.cards.c.CrimeNovelist.class)); cards.add(new SetCardInfo("Crimestopper Sprite", 49, Rarity.COMMON, mage.cards.c.CrimestopperSprite.class)); + cards.add(new SetCardInfo("Crowd-Control Warden", 193, Rarity.COMMON, mage.cards.c.CrowdControlWarden.class)); cards.add(new SetCardInfo("Cryptex", 251, Rarity.RARE, mage.cards.c.Cryptex.class)); cards.add(new SetCardInfo("Culvert Ambusher", 158, Rarity.UNCOMMON, mage.cards.c.CulvertAmbusher.class)); cards.add(new SetCardInfo("Curious Cadaver", 194, Rarity.UNCOMMON, mage.cards.c.CuriousCadaver.class)); @@ -100,6 +112,7 @@ public final class MurdersAtKarlovManor extends ExpansionSet { cards.add(new SetCardInfo("Evidence Examiner", 201, Rarity.UNCOMMON, mage.cards.e.EvidenceExaminer.class)); cards.add(new SetCardInfo("Exit Specialist", 55, Rarity.UNCOMMON, mage.cards.e.ExitSpecialist.class)); cards.add(new SetCardInfo("Expedited Inheritance", 123, Rarity.MYTHIC, mage.cards.e.ExpeditedInheritance.class)); + cards.add(new SetCardInfo("Extract a Confession", 84, Rarity.COMMON, mage.cards.e.ExtractAConfession.class)); cards.add(new SetCardInfo("Ezrim, Agency Chief", 202, Rarity.RARE, mage.cards.e.EzrimAgencyChief.class)); cards.add(new SetCardInfo("Fae Flight", 56, Rarity.UNCOMMON, mage.cards.f.FaeFlight.class)); cards.add(new SetCardInfo("Faerie Snoop", 203, Rarity.COMMON, mage.cards.f.FaerieSnoop.class)); @@ -113,6 +126,8 @@ public final class MurdersAtKarlovManor extends ExpansionSet { cards.add(new SetCardInfo("Forensic Researcher", 58, Rarity.UNCOMMON, mage.cards.f.ForensicResearcher.class)); cards.add(new SetCardInfo("Forest", 276, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Forum Familiar", 16, Rarity.UNCOMMON, mage.cards.f.ForumFamiliar.class)); + cards.add(new SetCardInfo("Frantic Scapegoat", 126, Rarity.UNCOMMON, mage.cards.f.FranticScapegoat.class)); + cards.add(new SetCardInfo("Fugitive Codebreaker", 127, Rarity.RARE, mage.cards.f.FugitiveCodebreaker.class)); cards.add(new SetCardInfo("Furtive Courier", 59, Rarity.UNCOMMON, mage.cards.f.FurtiveCourier.class)); cards.add(new SetCardInfo("Fuss // Bother", 248, Rarity.UNCOMMON, mage.cards.f.FussBother.class)); cards.add(new SetCardInfo("Gadget Technician", 204, Rarity.COMMON, mage.cards.g.GadgetTechnician.class)); @@ -195,6 +210,7 @@ public final class MurdersAtKarlovManor extends ExpansionSet { cards.add(new SetCardInfo("On the Job", 30, Rarity.COMMON, mage.cards.o.OnTheJob.class)); cards.add(new SetCardInfo("Out Cold", 66, Rarity.COMMON, mage.cards.o.OutCold.class)); cards.add(new SetCardInfo("Outrageous Robbery", 97, Rarity.RARE, mage.cards.o.OutrageousRobbery.class)); + cards.add(new SetCardInfo("Perimeter Enforcer", 31, Rarity.UNCOMMON, mage.cards.p.PerimeterEnforcer.class)); cards.add(new SetCardInfo("Person of Interest", 139, Rarity.COMMON, mage.cards.p.PersonOfInterest.class)); cards.add(new SetCardInfo("Persuasive Interrogators", 98, Rarity.UNCOMMON, mage.cards.p.PersuasiveInterrogators.class)); cards.add(new SetCardInfo("Pick Your Poison", 170, Rarity.COMMON, mage.cards.p.PickYourPoison.class)); @@ -204,6 +220,7 @@ public final class MurdersAtKarlovManor extends ExpansionSet { cards.add(new SetCardInfo("Presumed Dead", 100, Rarity.UNCOMMON, mage.cards.p.PresumedDead.class)); cards.add(new SetCardInfo("Private Eye", 223, Rarity.UNCOMMON, mage.cards.p.PrivateEye.class)); cards.add(new SetCardInfo("Proft's Eidetic Memory", 67, Rarity.RARE, mage.cards.p.ProftsEideticMemory.class)); + cards.add(new SetCardInfo("Projektor Inspector", 68, Rarity.COMMON, mage.cards.p.ProjektorInspector.class)); cards.add(new SetCardInfo("Public Thoroughfare", 265, Rarity.COMMON, mage.cards.p.PublicThoroughfare.class)); cards.add(new SetCardInfo("Push // Pull", 250, Rarity.UNCOMMON, mage.cards.p.PushPull.class)); cards.add(new SetCardInfo("Pyrotechnic Performer", 140, Rarity.RARE, mage.cards.p.PyrotechnicPerformer.class, NON_FULL_USE_VARIOUS)); @@ -269,6 +286,8 @@ public final class MurdersAtKarlovManor extends ExpansionSet { cards.add(new SetCardInfo("Underground Mortuary", 271, Rarity.RARE, mage.cards.u.UndergroundMortuary.class)); cards.add(new SetCardInfo("Undergrowth Recon", 181, Rarity.MYTHIC, mage.cards.u.UndergrowthRecon.class)); cards.add(new SetCardInfo("Unscrupulous Agent", 109, Rarity.COMMON, mage.cards.u.UnscrupulousAgent.class)); + cards.add(new SetCardInfo("Unyielding Gatekeeper", 35, Rarity.RARE, mage.cards.u.UnyieldingGatekeeper.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Unyielding Gatekeeper", 392, Rarity.RARE, mage.cards.u.UnyieldingGatekeeper.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Urgent Necropsy", 240, Rarity.MYTHIC, mage.cards.u.UrgentNecropsy.class)); cards.add(new SetCardInfo("Vein Ripper", 110, Rarity.MYTHIC, mage.cards.v.VeinRipper.class)); cards.add(new SetCardInfo("Vengeful Creeper", 182, Rarity.COMMON, mage.cards.v.VengefulCreeper.class)); diff --git a/Mage.Sets/src/mage/sets/MurdersAtKarlovManorCommander.java b/Mage.Sets/src/mage/sets/MurdersAtKarlovManorCommander.java index 4ba1405f3ea..1f864a1d69e 100644 --- a/Mage.Sets/src/mage/sets/MurdersAtKarlovManorCommander.java +++ b/Mage.Sets/src/mage/sets/MurdersAtKarlovManorCommander.java @@ -132,6 +132,8 @@ public final class MurdersAtKarlovManorCommander extends ExpansionSet { cards.add(new SetCardInfo("Hydroid Krasis", 212, Rarity.RARE, mage.cards.h.HydroidKrasis.class)); cards.add(new SetCardInfo("Idol of Oblivion", 229, Rarity.RARE, mage.cards.i.IdolOfOblivion.class)); cards.add(new SetCardInfo("Imperial Hellkite", 155, Rarity.RARE, mage.cards.i.ImperialHellkite.class)); + cards.add(new SetCardInfo("Innocuous Researcher", 38, Rarity.RARE, mage.cards.i.InnocuousResearcher.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Innocuous Researcher", 348, Rarity.RARE, mage.cards.i.InnocuousResearcher.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Inspiring Statuary", 230, Rarity.RARE, mage.cards.i.InspiringStatuary.class)); cards.add(new SetCardInfo("Irrigated Farmland", 267, Rarity.RARE, mage.cards.i.IrrigatedFarmland.class)); cards.add(new SetCardInfo("Jeska's Will", 156, Rarity.RARE, mage.cards.j.JeskasWill.class)); @@ -205,6 +207,7 @@ public final class MurdersAtKarlovManorCommander extends ExpansionSet { cards.add(new SetCardInfo("Price of Fame", 135, Rarity.UNCOMMON, mage.cards.p.PriceOfFame.class)); cards.add(new SetCardInfo("Printlifter Ooze", 40, Rarity.RARE, mage.cards.p.PrintlifterOoze.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Printlifter Ooze", 350, Rarity.RARE, mage.cards.p.PrintlifterOoze.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Prisoner's Dilemma", 34, Rarity.RARE, mage.cards.p.PrisonersDilemma.class)); cards.add(new SetCardInfo("Promise of Loyalty", 79, Rarity.RARE, mage.cards.p.PromiseOfLoyalty.class)); cards.add(new SetCardInfo("Psychosis Crawler", 234, Rarity.RARE, mage.cards.p.PsychosisCrawler.class)); cards.add(new SetCardInfo("Ravenous Chupacabra", 136, Rarity.UNCOMMON, mage.cards.r.RavenousChupacabra.class)); diff --git a/Mage.Sets/src/mage/sets/OutlawsOfThunderJunction.java b/Mage.Sets/src/mage/sets/OutlawsOfThunderJunction.java index f45f1059597..cde92b6a580 100644 --- a/Mage.Sets/src/mage/sets/OutlawsOfThunderJunction.java +++ b/Mage.Sets/src/mage/sets/OutlawsOfThunderJunction.java @@ -1,8 +1,14 @@ package mage.sets; +import mage.cards.Card; import mage.cards.ExpansionSet; +import mage.cards.repository.CardInfo; import mage.constants.Rarity; import mage.constants.SetType; +import mage.util.RandomUtil; + +import java.util.*; +import java.util.stream.Collectors; /** * @author TheElk801 @@ -19,13 +25,591 @@ public final class OutlawsOfThunderJunction extends ExpansionSet { super("Outlaws of Thunder Junction", "OTJ", ExpansionSet.buildDate(2024, 4, 19), SetType.EXPANSION); this.blockName = "Outlaws of Thunder Junction"; // for sorting in GUI this.hasBasicLands = true; - this.hasBoosters = false; // temporary + this.hasBoosters = true; + this.maxCardNumberInBooster = 276; - cards.add(new SetCardInfo("Forest", 276, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Hell to Pay", 126, Rarity.RARE, mage.cards.h.HellToPay.class)); - cards.add(new SetCardInfo("Island", 273, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Mountain", 275, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Plains", 272, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Swamp", 274, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Abraded Bluffs", 251, Rarity.COMMON, mage.cards.a.AbradedBluffs.class)); + cards.add(new SetCardInfo("Akul the Unrepentant", 189, Rarity.RARE, mage.cards.a.AkulTheUnrepentant.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Akul the Unrepentant", 346, Rarity.RARE, mage.cards.a.AkulTheUnrepentant.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Aloe Alchemist", 152, Rarity.UNCOMMON, mage.cards.a.AloeAlchemist.class)); + cards.add(new SetCardInfo("Ambush Gigapede", 77, Rarity.COMMON, mage.cards.a.AmbushGigapede.class)); + cards.add(new SetCardInfo("Ankle Biter", 153, Rarity.COMMON, mage.cards.a.AnkleBiter.class)); + cards.add(new SetCardInfo("Annie Flash, the Veteran", 190, Rarity.MYTHIC, mage.cards.a.AnnieFlashTheVeteran.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Annie Flash, the Veteran", 291, Rarity.MYTHIC, mage.cards.a.AnnieFlashTheVeteran.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Annie Joins Up", 191, Rarity.RARE, mage.cards.a.AnnieJoinsUp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Annie Joins Up", 347, Rarity.RARE, mage.cards.a.AnnieJoinsUp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Another Round", 1, Rarity.RARE, mage.cards.a.AnotherRound.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Another Round", 307, Rarity.RARE, mage.cards.a.AnotherRound.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Archangel of Tithes", 2, Rarity.MYTHIC, mage.cards.a.ArchangelOfTithes.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Archangel of Tithes", 308, Rarity.MYTHIC, mage.cards.a.ArchangelOfTithes.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Archmage's Newt", 316, Rarity.RARE, mage.cards.a.ArchmagesNewt.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Archmage's Newt", 39, Rarity.RARE, mage.cards.a.ArchmagesNewt.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Arid Archway", 252, Rarity.UNCOMMON, mage.cards.a.AridArchway.class)); + cards.add(new SetCardInfo("Armored Armadillo", 3, Rarity.COMMON, mage.cards.a.ArmoredArmadillo.class)); + cards.add(new SetCardInfo("Assimilation Aegis", 192, Rarity.MYTHIC, mage.cards.a.AssimilationAegis.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Assimilation Aegis", 348, Rarity.MYTHIC, mage.cards.a.AssimilationAegis.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("At Knifepoint", 193, Rarity.UNCOMMON, mage.cards.a.AtKnifepoint.class)); + cards.add(new SetCardInfo("Aven Interrupter", 309, Rarity.RARE, mage.cards.a.AvenInterrupter.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Aven Interrupter", 4, Rarity.RARE, mage.cards.a.AvenInterrupter.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Badlands Revival", 194, Rarity.UNCOMMON, mage.cards.b.BadlandsRevival.class)); + cards.add(new SetCardInfo("Bandit's Haul", 240, Rarity.UNCOMMON, mage.cards.b.BanditsHaul.class)); + cards.add(new SetCardInfo("Baron Bertram Graywater", 195, Rarity.UNCOMMON, mage.cards.b.BaronBertramGraywater.class)); + cards.add(new SetCardInfo("Beastbond Outcaster", 154, Rarity.UNCOMMON, mage.cards.b.BeastbondOutcaster.class)); + cards.add(new SetCardInfo("Betrayal at the Vault", 155, Rarity.UNCOMMON, mage.cards.b.BetrayalAtTheVault.class)); + cards.add(new SetCardInfo("Binding Negotiation", 78, Rarity.UNCOMMON, mage.cards.b.BindingNegotiation.class)); + cards.add(new SetCardInfo("Blacksnag Buzzard", 79, Rarity.COMMON, mage.cards.b.BlacksnagBuzzard.class)); + cards.add(new SetCardInfo("Blood Hustler", 80, Rarity.UNCOMMON, mage.cards.b.BloodHustler.class)); + cards.add(new SetCardInfo("Blooming Marsh", 266, Rarity.RARE, mage.cards.b.BloomingMarsh.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Blooming Marsh", 300, Rarity.RARE, mage.cards.b.BloomingMarsh.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Boneyard Desecrator", 81, Rarity.COMMON, mage.cards.b.BoneyardDesecrator.class)); + cards.add(new SetCardInfo("Bonny Pall, Clearcutter", 196, Rarity.RARE, mage.cards.b.BonnyPallClearcutter.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bonny Pall, Clearcutter", 349, Rarity.RARE, mage.cards.b.BonnyPallClearcutter.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Boom Box", 241, Rarity.UNCOMMON, mage.cards.b.BoomBox.class)); + cards.add(new SetCardInfo("Botanical Sanctum", 267, Rarity.RARE, mage.cards.b.BotanicalSanctum.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Botanical Sanctum", 301, Rarity.RARE, mage.cards.b.BotanicalSanctum.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bounding Felidar", 5, Rarity.UNCOMMON, mage.cards.b.BoundingFelidar.class)); + cards.add(new SetCardInfo("Bovine Intervention", 6, Rarity.UNCOMMON, mage.cards.b.BovineIntervention.class)); + cards.add(new SetCardInfo("Breeches, the Blastmaker", 197, Rarity.RARE, mage.cards.b.BreechesTheBlastmaker.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Breeches, the Blastmaker", 292, Rarity.RARE, mage.cards.b.BreechesTheBlastmaker.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bridled Bighorn", 7, Rarity.COMMON, mage.cards.b.BridledBighorn.class)); + cards.add(new SetCardInfo("Brimstone Roundup", 115, Rarity.UNCOMMON, mage.cards.b.BrimstoneRoundup.class)); + cards.add(new SetCardInfo("Bristlepack Sentry", 156, Rarity.COMMON, mage.cards.b.BristlepackSentry.class)); + cards.add(new SetCardInfo("Bristling Backwoods", 253, Rarity.COMMON, mage.cards.b.BristlingBackwoods.class)); + cards.add(new SetCardInfo("Bristly Bill, Spine Sower", 157, Rarity.MYTHIC, mage.cards.b.BristlyBillSpineSower.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bristly Bill, Spine Sower", 338, Rarity.MYTHIC, mage.cards.b.BristlyBillSpineSower.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bruse Tarl, Roving Rancher", 198, Rarity.RARE, mage.cards.b.BruseTarlRovingRancher.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bruse Tarl, Roving Rancher", 350, Rarity.RARE, mage.cards.b.BruseTarlRovingRancher.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bucolic Ranch", 265, Rarity.UNCOMMON, mage.cards.b.BucolicRanch.class)); + cards.add(new SetCardInfo("Cactarantula", 158, Rarity.COMMON, mage.cards.c.Cactarantula.class)); + cards.add(new SetCardInfo("Cactusfolk Sureshot", 199, Rarity.UNCOMMON, mage.cards.c.CactusfolkSureshot.class)); + cards.add(new SetCardInfo("Calamity, Galloping Inferno", 116, Rarity.RARE, mage.cards.c.CalamityGallopingInferno.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Calamity, Galloping Inferno", 330, Rarity.RARE, mage.cards.c.CalamityGallopingInferno.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Canyon Crab", 40, Rarity.UNCOMMON, mage.cards.c.CanyonCrab.class)); + cards.add(new SetCardInfo("Caught in the Crossfire", 117, Rarity.UNCOMMON, mage.cards.c.CaughtInTheCrossfire.class)); + cards.add(new SetCardInfo("Caustic Bronco", 324, Rarity.RARE, mage.cards.c.CausticBronco.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Caustic Bronco", 82, Rarity.RARE, mage.cards.c.CausticBronco.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Claim Jumper", 310, Rarity.RARE, mage.cards.c.ClaimJumper.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Claim Jumper", 8, Rarity.RARE, mage.cards.c.ClaimJumper.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Colossal Rattlewurm", 159, Rarity.RARE, mage.cards.c.ColossalRattlewurm.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Colossal Rattlewurm", 339, Rarity.RARE, mage.cards.c.ColossalRattlewurm.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Concealed Courtyard", 268, Rarity.RARE, mage.cards.c.ConcealedCourtyard.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Concealed Courtyard", 302, Rarity.RARE, mage.cards.c.ConcealedCourtyard.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Conduit Pylons", 254, Rarity.COMMON, mage.cards.c.ConduitPylons.class)); + cards.add(new SetCardInfo("Congregation Gryff", 200, Rarity.UNCOMMON, mage.cards.c.CongregationGryff.class)); + cards.add(new SetCardInfo("Consuming Ashes", 83, Rarity.COMMON, mage.cards.c.ConsumingAshes.class)); + cards.add(new SetCardInfo("Corrupted Conviction", 84, Rarity.COMMON, mage.cards.c.CorruptedConviction.class)); + cards.add(new SetCardInfo("Creosote Heath", 255, Rarity.COMMON, mage.cards.c.CreosoteHeath.class)); + cards.add(new SetCardInfo("Cunning Coyote", 118, Rarity.UNCOMMON, mage.cards.c.CunningCoyote.class)); + cards.add(new SetCardInfo("Dance of the Tumbleweeds", 160, Rarity.COMMON, mage.cards.d.DanceOfTheTumbleweeds.class)); + cards.add(new SetCardInfo("Daring Thunder-Thief", 41, Rarity.COMMON, mage.cards.d.DaringThunderThief.class)); + cards.add(new SetCardInfo("Deadeye Duelist", 119, Rarity.COMMON, mage.cards.d.DeadeyeDuelist.class)); + cards.add(new SetCardInfo("Deepmuck Desperado", 42, Rarity.UNCOMMON, mage.cards.d.DeepmuckDesperado.class)); + cards.add(new SetCardInfo("Demonic Ruckus", 120, Rarity.UNCOMMON, mage.cards.d.DemonicRuckus.class)); + cards.add(new SetCardInfo("Desert's Due", 85, Rarity.COMMON, mage.cards.d.DesertsDue.class)); + cards.add(new SetCardInfo("Desperate Bloodseeker", 86, Rarity.COMMON, mage.cards.d.DesperateBloodseeker.class)); + cards.add(new SetCardInfo("Discerning Peddler", 121, Rarity.COMMON, mage.cards.d.DiscerningPeddler.class)); + cards.add(new SetCardInfo("Djinn of Fool's Fall", 43, Rarity.COMMON, mage.cards.d.DjinnOfFoolsFall.class)); + cards.add(new SetCardInfo("Doc Aurlock, Grizzled Genius", 201, Rarity.UNCOMMON, mage.cards.d.DocAurlockGrizzledGenius.class)); + cards.add(new SetCardInfo("Double Down", 317, Rarity.MYTHIC, mage.cards.d.DoubleDown.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Double Down", 44, Rarity.MYTHIC, mage.cards.d.DoubleDown.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Drover Grizzly", 161, Rarity.COMMON, mage.cards.d.DroverGrizzly.class)); + cards.add(new SetCardInfo("Duelist of the Mind", 318, Rarity.RARE, mage.cards.d.DuelistOfTheMind.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Duelist of the Mind", 45, Rarity.RARE, mage.cards.d.DuelistOfTheMind.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Dust Animus", 311, Rarity.RARE, mage.cards.d.DustAnimus.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Dust Animus", 9, Rarity.RARE, mage.cards.d.DustAnimus.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Emergent Haunting", 46, Rarity.UNCOMMON, mage.cards.e.EmergentHaunting.class)); + cards.add(new SetCardInfo("Eriette's Lullaby", 10, Rarity.COMMON, mage.cards.e.EriettesLullaby.class)); + cards.add(new SetCardInfo("Eriette, the Beguiler", 202, Rarity.RARE, mage.cards.e.ErietteTheBeguiler.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Eriette, the Beguiler", 293, Rarity.RARE, mage.cards.e.ErietteTheBeguiler.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Eroded Canyon", 256, Rarity.COMMON, mage.cards.e.ErodedCanyon.class)); + cards.add(new SetCardInfo("Ertha Jo, Frontier Mentor", 203, Rarity.UNCOMMON, mage.cards.e.ErthaJoFrontierMentor.class)); + cards.add(new SetCardInfo("Explosive Derailment", 122, Rarity.COMMON, mage.cards.e.ExplosiveDerailment.class)); + cards.add(new SetCardInfo("Failed Fording", 47, Rarity.COMMON, mage.cards.f.FailedFording.class)); + cards.add(new SetCardInfo("Fake Your Own Death", 87, Rarity.COMMON, mage.cards.f.FakeYourOwnDeath.class)); + cards.add(new SetCardInfo("Fblthp, Lost on the Range", 319, Rarity.RARE, mage.cards.f.FblthpLostOnTheRange.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Fblthp, Lost on the Range", 48, Rarity.RARE, mage.cards.f.FblthpLostOnTheRange.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ferocification", 123, Rarity.UNCOMMON, mage.cards.f.Ferocification.class)); + cards.add(new SetCardInfo("Festering Gulch", 257, Rarity.COMMON, mage.cards.f.FesteringGulch.class)); + cards.add(new SetCardInfo("Final Showdown", 11, Rarity.MYTHIC, mage.cards.f.FinalShowdown.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Final Showdown", 312, Rarity.MYTHIC, mage.cards.f.FinalShowdown.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Fleeting Reflection", 49, Rarity.UNCOMMON, mage.cards.f.FleetingReflection.class)); + 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("Forlorn Flats", 258, Rarity.COMMON, mage.cards.f.ForlornFlats.class)); + cards.add(new SetCardInfo("Form a Posse", 204, Rarity.UNCOMMON, mage.cards.f.FormAPosse.class)); + cards.add(new SetCardInfo("Forsaken Miner", 88, Rarity.UNCOMMON, mage.cards.f.ForsakenMiner.class)); + cards.add(new SetCardInfo("Fortune, Loyal Steed", 12, Rarity.RARE, mage.cards.f.FortuneLoyalSteed.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Fortune, Loyal Steed", 313, Rarity.RARE, mage.cards.f.FortuneLoyalSteed.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Freestrider Commando", 162, Rarity.COMMON, mage.cards.f.FreestriderCommando.class)); + cards.add(new SetCardInfo("Freestrider Lookout", 163, Rarity.RARE, mage.cards.f.FreestriderLookout.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Freestrider Lookout", 340, Rarity.RARE, mage.cards.f.FreestriderLookout.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Frontier Seeker", 13, Rarity.UNCOMMON, mage.cards.f.FrontierSeeker.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Frontier Seeker", 368, Rarity.UNCOMMON, mage.cards.f.FrontierSeeker.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Full Steam Ahead", 164, Rarity.UNCOMMON, mage.cards.f.FullSteamAhead.class)); + cards.add(new SetCardInfo("Geralf, the Fleshwright", 287, Rarity.MYTHIC, mage.cards.g.GeralfTheFleshwright.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Geralf, the Fleshwright", 50, Rarity.MYTHIC, mage.cards.g.GeralfTheFleshwright.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Getaway Glamer", 14, Rarity.UNCOMMON, mage.cards.g.GetawayGlamer.class)); + cards.add(new SetCardInfo("Geyser Drake", 51, Rarity.COMMON, mage.cards.g.GeyserDrake.class)); + cards.add(new SetCardInfo("Ghired, Mirror of the Wilds", 205, Rarity.MYTHIC, mage.cards.g.GhiredMirrorOfTheWilds.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ghired, Mirror of the Wilds", 351, Rarity.MYTHIC, mage.cards.g.GhiredMirrorOfTheWilds.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Giant Beaver", 165, Rarity.COMMON, mage.cards.g.GiantBeaver.class)); + cards.add(new SetCardInfo("Gila Courser", 124, Rarity.UNCOMMON, mage.cards.g.GilaCourser.class)); + cards.add(new SetCardInfo("Gisa, the Hellraiser", 288, Rarity.MYTHIC, mage.cards.g.GisaTheHellraiser.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Gisa, the Hellraiser", 89, Rarity.MYTHIC, mage.cards.g.GisaTheHellraiser.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Gold Pan", 242, Rarity.COMMON, mage.cards.g.GoldPan.class)); + cards.add(new SetCardInfo("Gold Rush", 166, Rarity.UNCOMMON, mage.cards.g.GoldRush.class)); + cards.add(new SetCardInfo("Goldvein Hydra", 167, Rarity.MYTHIC, mage.cards.g.GoldveinHydra.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Goldvein Hydra", 341, Rarity.MYTHIC, mage.cards.g.GoldveinHydra.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Great Train Heist", 125, Rarity.RARE, mage.cards.g.GreatTrainHeist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Great Train Heist", 331, Rarity.RARE, mage.cards.g.GreatTrainHeist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hardbristle Bandit", 168, Rarity.COMMON, mage.cards.h.HardbristleBandit.class)); + cards.add(new SetCardInfo("Harrier Strix", 52, Rarity.COMMON, mage.cards.h.HarrierStrix.class)); + cards.add(new SetCardInfo("Hell to Pay", 126, Rarity.RARE, mage.cards.h.HellToPay.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hell to Pay", 332, Rarity.RARE, mage.cards.h.HellToPay.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hellspur Brute", 127, Rarity.UNCOMMON, mage.cards.h.HellspurBrute.class)); + cards.add(new SetCardInfo("Hellspur Posse Boss", 128, Rarity.RARE, mage.cards.h.HellspurPosseBoss.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hellspur Posse Boss", 333, Rarity.RARE, mage.cards.h.HellspurPosseBoss.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("High Noon", 15, Rarity.RARE, mage.cards.h.HighNoon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("High Noon", 314, Rarity.RARE, mage.cards.h.HighNoon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Highway Robbery", 129, Rarity.COMMON, mage.cards.h.HighwayRobbery.class)); + cards.add(new SetCardInfo("Hollow Marauder", 90, Rarity.UNCOMMON, mage.cards.h.HollowMarauder.class)); + cards.add(new SetCardInfo("Holy Cow", 16, Rarity.COMMON, mage.cards.h.HolyCow.class)); + cards.add(new SetCardInfo("Honest Rutstein", 207, Rarity.UNCOMMON, mage.cards.h.HonestRutstein.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Honest Rutstein", 370, Rarity.UNCOMMON, mage.cards.h.HonestRutstein.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Insatiable Avarice", 325, Rarity.RARE, mage.cards.i.InsatiableAvarice.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Insatiable Avarice", 91, Rarity.RARE, mage.cards.i.InsatiableAvarice.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Inspiring Vantage", 269, Rarity.RARE, mage.cards.i.InspiringVantage.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Inspiring Vantage", 303, Rarity.RARE, mage.cards.i.InspiringVantage.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Intimidation Campaign", 208, Rarity.UNCOMMON, mage.cards.i.IntimidationCampaign.class)); + cards.add(new SetCardInfo("Intrepid Stablemaster", 169, Rarity.UNCOMMON, mage.cards.i.IntrepidStablemaster.class)); + cards.add(new SetCardInfo("Inventive Wingsmith", 17, Rarity.COMMON, mage.cards.i.InventiveWingsmith.class)); + cards.add(new SetCardInfo("Irascible Wolverine", 130, Rarity.COMMON, mage.cards.i.IrascibleWolverine.class)); + cards.add(new SetCardInfo("Iron-Fist Pulverizer", 131, Rarity.COMMON, mage.cards.i.IronFistPulverizer.class)); + 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("Jace Reawakened", 271, Rarity.MYTHIC, mage.cards.j.JaceReawakened.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Jace Reawakened", 306, Rarity.MYTHIC, mage.cards.j.JaceReawakened.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Jagged Barrens", 259, Rarity.COMMON, mage.cards.j.JaggedBarrens.class)); + cards.add(new SetCardInfo("Jailbreak Scheme", 53, Rarity.COMMON, mage.cards.j.JailbreakScheme.class)); + cards.add(new SetCardInfo("Jem Lightfoote, Sky Explorer", 209, Rarity.UNCOMMON, mage.cards.j.JemLightfooteSkyExplorer.class)); + cards.add(new SetCardInfo("Jolene, Plundering Pugilist", 210, Rarity.UNCOMMON, mage.cards.j.JolenePlunderingPugilist.class)); + cards.add(new SetCardInfo("Kaervek, the Punisher", 289, Rarity.RARE, mage.cards.k.KaervekThePunisher.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Kaervek, the Punisher", 92, Rarity.RARE, mage.cards.k.KaervekThePunisher.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Kambal, Profiteering Mayor", 211, Rarity.RARE, mage.cards.k.KambalProfiteeringMayor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Kambal, Profiteering Mayor", 353, Rarity.RARE, mage.cards.k.KambalProfiteeringMayor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Kellan Joins Up", 212, Rarity.RARE, mage.cards.k.KellanJoinsUp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Kellan Joins Up", 354, Rarity.RARE, mage.cards.k.KellanJoinsUp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Kellan, the Kid", 213, Rarity.MYTHIC, mage.cards.k.KellanTheKid.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Kellan, the Kid", 294, Rarity.MYTHIC, mage.cards.k.KellanTheKid.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Kraum, Violent Cacophony", 214, Rarity.UNCOMMON, mage.cards.k.KraumViolentCacophony.class)); + cards.add(new SetCardInfo("Lassoed by the Law", 18, Rarity.UNCOMMON, mage.cards.l.LassoedByTheLaw.class)); + cards.add(new SetCardInfo("Laughing Jasper Flint", 215, Rarity.RARE, mage.cards.l.LaughingJasperFlint.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Laughing Jasper Flint", 355, Rarity.RARE, mage.cards.l.LaughingJasperFlint.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lavaspur Boots", 243, Rarity.UNCOMMON, mage.cards.l.LavaspurBoots.class)); + cards.add(new SetCardInfo("Lazav, Familiar Stranger", 216, Rarity.UNCOMMON, mage.cards.l.LazavFamiliarStranger.class)); + cards.add(new SetCardInfo("Lilah, Undefeated Slickshot", 217, Rarity.RARE, mage.cards.l.LilahUndefeatedSlickshot.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lilah, Undefeated Slickshot", 356, Rarity.RARE, mage.cards.l.LilahUndefeatedSlickshot.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lively Dirge", 93, Rarity.UNCOMMON, mage.cards.l.LivelyDirge.class)); + cards.add(new SetCardInfo("Loan Shark", 55, Rarity.COMMON, mage.cards.l.LoanShark.class)); + cards.add(new SetCardInfo("Lonely Arroyo", 260, Rarity.COMMON, mage.cards.l.LonelyArroyo.class)); + cards.add(new SetCardInfo("Longhorn Sharpshooter", 132, Rarity.UNCOMMON, mage.cards.l.LonghornSharpshooter.class)); + cards.add(new SetCardInfo("Lush Oasis", 261, Rarity.COMMON, mage.cards.l.LushOasis.class)); + cards.add(new SetCardInfo("Luxurious Locomotive", 244, Rarity.UNCOMMON, mage.cards.l.LuxuriousLocomotive.class)); + cards.add(new SetCardInfo("Magda, the Hoardmaster", 133, Rarity.RARE, mage.cards.m.MagdaTheHoardmaster.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Magda, the Hoardmaster", 334, Rarity.RARE, mage.cards.m.MagdaTheHoardmaster.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Magda, the Hoardmaster", 374, Rarity.RARE, mage.cards.m.MagdaTheHoardmaster.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Magebane Lizard", 134, Rarity.UNCOMMON, mage.cards.m.MagebaneLizard.class)); + cards.add(new SetCardInfo("Make Your Own Luck", 218, Rarity.UNCOMMON, mage.cards.m.MakeYourOwnLuck.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Make Your Own Luck", 371, Rarity.UNCOMMON, mage.cards.m.MakeYourOwnLuck.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Malcolm, the Eyes", 219, Rarity.RARE, mage.cards.m.MalcolmTheEyes.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Malcolm, the Eyes", 295, Rarity.RARE, mage.cards.m.MalcolmTheEyes.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Map the Frontier", 170, Rarity.UNCOMMON, mage.cards.m.MapTheFrontier.class)); + cards.add(new SetCardInfo("Marauding Sphinx", 56, Rarity.UNCOMMON, mage.cards.m.MaraudingSphinx.class)); + cards.add(new SetCardInfo("Marchesa, Dealer of Death", 220, Rarity.RARE, mage.cards.m.MarchesaDealerOfDeath.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Marchesa, Dealer of Death", 357, Rarity.RARE, mage.cards.m.MarchesaDealerOfDeath.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Metamorphic Blast", 57, Rarity.UNCOMMON, mage.cards.m.MetamorphicBlast.class)); + cards.add(new SetCardInfo("Mine Raider", 135, Rarity.COMMON, mage.cards.m.MineRaider.class)); + cards.add(new SetCardInfo("Mirage Mesa", 262, Rarity.COMMON, mage.cards.m.MirageMesa.class)); + cards.add(new SetCardInfo("Miriam, Herd Whisperer", 221, Rarity.UNCOMMON, mage.cards.m.MiriamHerdWhisperer.class)); + cards.add(new SetCardInfo("Mobile Homestead", 245, Rarity.UNCOMMON, mage.cards.m.MobileHomestead.class)); + 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("Mourner's Surprise", 94, Rarity.COMMON, mage.cards.m.MournersSurprise.class)); + cards.add(new SetCardInfo("Mystical Tether", 19, Rarity.COMMON, mage.cards.m.MysticalTether.class)); + cards.add(new SetCardInfo("Neutralize the Guards", 95, Rarity.UNCOMMON, mage.cards.n.NeutralizeTheGuards.class)); + cards.add(new SetCardInfo("Nezumi Linkbreaker", 96, Rarity.COMMON, mage.cards.n.NezumiLinkbreaker.class)); + cards.add(new SetCardInfo("Nimble Brigand", 58, Rarity.UNCOMMON, mage.cards.n.NimbleBrigand.class)); + cards.add(new SetCardInfo("Nurturing Pixie", 20, Rarity.UNCOMMON, mage.cards.n.NurturingPixie.class)); + cards.add(new SetCardInfo("Oasis Gardener", 246, Rarity.COMMON, mage.cards.o.OasisGardener.class)); + cards.add(new SetCardInfo("Obeka, Splitter of Seconds", 222, Rarity.RARE, mage.cards.o.ObekaSplitterOfSeconds.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Obeka, Splitter of Seconds", 358, Rarity.RARE, mage.cards.o.ObekaSplitterOfSeconds.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Oko, the Ringleader", 223, Rarity.MYTHIC, mage.cards.o.OkoTheRingleader.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Oko, the Ringleader", 296, Rarity.MYTHIC, mage.cards.o.OkoTheRingleader.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Oko, the Ringleader", 305, Rarity.MYTHIC, mage.cards.o.OkoTheRingleader.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Omenport Vigilante", 21, Rarity.UNCOMMON, mage.cards.o.OmenportVigilante.class)); + cards.add(new SetCardInfo("One Last Job", 22, Rarity.RARE, mage.cards.o.OneLastJob.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("One Last Job", 315, Rarity.RARE, mage.cards.o.OneLastJob.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ornery Tumblewagg", 171, Rarity.RARE, mage.cards.o.OrneryTumblewagg.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ornery Tumblewagg", 342, Rarity.RARE, mage.cards.o.OrneryTumblewagg.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Outcaster Greenblade", 172, Rarity.UNCOMMON, mage.cards.o.OutcasterGreenblade.class)); + cards.add(new SetCardInfo("Outcaster Trailblazer", 173, Rarity.RARE, mage.cards.o.OutcasterTrailblazer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Outcaster Trailblazer", 343, Rarity.RARE, mage.cards.o.OutcasterTrailblazer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Outlaw Medic", 23, Rarity.COMMON, mage.cards.o.OutlawMedic.class)); + cards.add(new SetCardInfo("Outlaw Stitcher", 59, Rarity.UNCOMMON, mage.cards.o.OutlawStitcher.class)); + cards.add(new SetCardInfo("Outlaws' Fury", 136, Rarity.COMMON, mage.cards.o.OutlawsFury.class)); + cards.add(new SetCardInfo("Overzealous Muscle", 97, Rarity.COMMON, mage.cards.o.OverzealousMuscle.class)); + cards.add(new SetCardInfo("Patient Naturalist", 174, Rarity.COMMON, mage.cards.p.PatientNaturalist.class)); + cards.add(new SetCardInfo("Peerless Ropemaster", 60, Rarity.COMMON, mage.cards.p.PeerlessRopemaster.class)); + cards.add(new SetCardInfo("Phantom Interference", 61, Rarity.COMMON, mage.cards.p.PhantomInterference.class)); + cards.add(new SetCardInfo("Pillage the Bog", 224, Rarity.RARE, mage.cards.p.PillageTheBog.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Pillage the Bog", 359, Rarity.RARE, mage.cards.p.PillageTheBog.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Pitiless Carnage", 326, Rarity.RARE, mage.cards.p.PitilessCarnage.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Pitiless Carnage", 98, Rarity.RARE, mage.cards.p.PitilessCarnage.class, NON_FULL_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("Plan the Heist", 62, Rarity.UNCOMMON, mage.cards.p.PlanTheHeist.class)); + cards.add(new SetCardInfo("Prairie Dog", 24, Rarity.UNCOMMON, mage.cards.p.PrairieDog.class)); + cards.add(new SetCardInfo("Prickly Pair", 137, Rarity.COMMON, mage.cards.p.PricklyPair.class)); + cards.add(new SetCardInfo("Prosperity Tycoon", 25, Rarity.UNCOMMON, mage.cards.p.ProsperityTycoon.class)); + cards.add(new SetCardInfo("Quick Draw", 138, Rarity.COMMON, mage.cards.q.QuickDraw.class)); + cards.add(new SetCardInfo("Quilled Charger", 139, Rarity.COMMON, mage.cards.q.QuilledCharger.class)); + cards.add(new SetCardInfo("Railway Brawler", 175, Rarity.MYTHIC, mage.cards.r.RailwayBrawler.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Railway Brawler", 344, Rarity.MYTHIC, mage.cards.r.RailwayBrawler.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Rakdos Joins Up", 225, Rarity.RARE, mage.cards.r.RakdosJoinsUp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Rakdos Joins Up", 360, Rarity.RARE, mage.cards.r.RakdosJoinsUp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Rakdos, the Muscle", 226, Rarity.MYTHIC, mage.cards.r.RakdosTheMuscle.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Rakdos, the Muscle", 297, Rarity.MYTHIC, mage.cards.r.RakdosTheMuscle.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Rakish Crew", 99, Rarity.UNCOMMON, mage.cards.r.RakishCrew.class)); + cards.add(new SetCardInfo("Rambling Possum", 176, Rarity.UNCOMMON, mage.cards.r.RamblingPossum.class)); + cards.add(new SetCardInfo("Rattleback Apothecary", 100, Rarity.UNCOMMON, mage.cards.r.RattlebackApothecary.class)); + cards.add(new SetCardInfo("Raucous Entertainer", 177, Rarity.UNCOMMON, mage.cards.r.RaucousEntertainer.class)); + cards.add(new SetCardInfo("Raven of Fell Omens", 101, Rarity.COMMON, mage.cards.r.RavenOfFellOmens.class)); + cards.add(new SetCardInfo("Razzle-Dazzler", 63, Rarity.COMMON, mage.cards.r.RazzleDazzler.class)); + cards.add(new SetCardInfo("Reach for the Sky", 178, Rarity.COMMON, mage.cards.r.ReachForTheSky.class)); + cards.add(new SetCardInfo("Reckless Lackey", 140, Rarity.COMMON, mage.cards.r.RecklessLackey.class)); + cards.add(new SetCardInfo("Redrock Sentinel", 247, Rarity.UNCOMMON, mage.cards.r.RedrockSentinel.class)); + cards.add(new SetCardInfo("Requisition Raid", 26, Rarity.UNCOMMON, mage.cards.r.RequisitionRaid.class)); + cards.add(new SetCardInfo("Resilient Roadrunner", 141, Rarity.UNCOMMON, mage.cards.r.ResilientRoadrunner.class)); + cards.add(new SetCardInfo("Return the Favor", 142, Rarity.UNCOMMON, mage.cards.r.ReturnTheFavor.class)); + cards.add(new SetCardInfo("Rictus Robber", 102, Rarity.UNCOMMON, mage.cards.r.RictusRobber.class)); + cards.add(new SetCardInfo("Riku of Many Paths", 227, Rarity.RARE, mage.cards.r.RikuOfManyPaths.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Riku of Many Paths", 361, Rarity.RARE, mage.cards.r.RikuOfManyPaths.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Rise of the Varmints", 179, Rarity.UNCOMMON, mage.cards.r.RiseOfTheVarmints.class)); + cards.add(new SetCardInfo("Rodeo Pyromancers", 143, Rarity.COMMON, mage.cards.r.RodeoPyromancers.class)); + cards.add(new SetCardInfo("Rooftop Assassin", 103, Rarity.COMMON, mage.cards.r.RooftopAssassin.class)); + cards.add(new SetCardInfo("Roxanne, Starfall Savant", 228, Rarity.RARE, mage.cards.r.RoxanneStarfallSavant.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Roxanne, Starfall Savant", 362, Rarity.RARE, mage.cards.r.RoxanneStarfallSavant.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Rush of Dread", 104, Rarity.RARE, mage.cards.r.RushOfDread.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Rush of Dread", 327, Rarity.RARE, mage.cards.r.RushOfDread.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Rustler Rampage", 27, Rarity.UNCOMMON, mage.cards.r.RustlerRampage.class)); + cards.add(new SetCardInfo("Ruthless Lawbringer", 229, Rarity.UNCOMMON, mage.cards.r.RuthlessLawbringer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ruthless Lawbringer", 372, Rarity.UNCOMMON, mage.cards.r.RuthlessLawbringer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sandstorm Verge", 263, Rarity.UNCOMMON, mage.cards.s.SandstormVerge.class)); + cards.add(new SetCardInfo("Satoru, the Infiltrator", 230, Rarity.RARE, mage.cards.s.SatoruTheInfiltrator.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Satoru, the Infiltrator", 298, Rarity.RARE, mage.cards.s.SatoruTheInfiltrator.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Scalestorm Summoner", 144, Rarity.UNCOMMON, mage.cards.s.ScalestormSummoner.class)); + cards.add(new SetCardInfo("Scorching Shot", 145, Rarity.UNCOMMON, mage.cards.s.ScorchingShot.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Scorching Shot", 369, Rarity.UNCOMMON, mage.cards.s.ScorchingShot.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Seize the Secrets", 64, Rarity.COMMON, mage.cards.s.SeizeTheSecrets.class)); + cards.add(new SetCardInfo("Selvala, Eager Trailblazer", 231, Rarity.MYTHIC, mage.cards.s.SelvalaEagerTrailblazer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Selvala, Eager Trailblazer", 363, Rarity.MYTHIC, mage.cards.s.SelvalaEagerTrailblazer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Seraphic Steed", 232, Rarity.RARE, mage.cards.s.SeraphicSteed.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Seraphic Steed", 364, Rarity.RARE, mage.cards.s.SeraphicSteed.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Servant of the Stinger", 105, Rarity.UNCOMMON, mage.cards.s.ServantOfTheStinger.class)); + cards.add(new SetCardInfo("Shackle Slinger", 65, Rarity.UNCOMMON, mage.cards.s.ShackleSlinger.class)); + cards.add(new SetCardInfo("Shepherd of the Clouds", 28, Rarity.UNCOMMON, mage.cards.s.ShepherdOfTheClouds.class)); + cards.add(new SetCardInfo("Sheriff of Safe Passage", 29, Rarity.UNCOMMON, mage.cards.s.SheriffOfSafePassage.class)); + cards.add(new SetCardInfo("Shifting Grift", 66, Rarity.UNCOMMON, mage.cards.s.ShiftingGrift.class)); + cards.add(new SetCardInfo("Shoot the Sheriff", 106, Rarity.UNCOMMON, mage.cards.s.ShootTheSheriff.class)); + cards.add(new SetCardInfo("Silver Deputy", 248, Rarity.COMMON, mage.cards.s.SilverDeputy.class)); + cards.add(new SetCardInfo("Skulduggery", 107, Rarity.COMMON, mage.cards.s.Skulduggery.class)); + cards.add(new SetCardInfo("Slick Sequence", 233, Rarity.UNCOMMON, mage.cards.s.SlickSequence.class)); + cards.add(new SetCardInfo("Slickshot Lockpicker", 67, Rarity.UNCOMMON, mage.cards.s.SlickshotLockpicker.class)); + cards.add(new SetCardInfo("Slickshot Show-Off", 146, Rarity.RARE, mage.cards.s.SlickshotShowOff.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Slickshot Show-Off", 335, Rarity.RARE, mage.cards.s.SlickshotShowOff.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Slickshot Vault-Buster", 68, Rarity.COMMON, mage.cards.s.SlickshotVaultBuster.class)); + cards.add(new SetCardInfo("Smuggler's Surprise", 180, Rarity.RARE, mage.cards.s.SmugglersSurprise.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Smuggler's Surprise", 345, Rarity.RARE, mage.cards.s.SmugglersSurprise.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Snakeskin Veil", 181, Rarity.COMMON, mage.cards.s.SnakeskinVeil.class)); + cards.add(new SetCardInfo("Soured Springs", 264, Rarity.COMMON, mage.cards.s.SouredSprings.class)); + cards.add(new SetCardInfo("Spinewoods Armadillo", 182, Rarity.UNCOMMON, mage.cards.s.SpinewoodsArmadillo.class)); + cards.add(new SetCardInfo("Spinewoods Paladin", 183, Rarity.COMMON, mage.cards.s.SpinewoodsPaladin.class)); + cards.add(new SetCardInfo("Spirebluff Canal", 270, Rarity.RARE, mage.cards.s.SpirebluffCanal.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spirebluff Canal", 304, Rarity.RARE, mage.cards.s.SpirebluffCanal.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spring Splasher", 69, Rarity.COMMON, mage.cards.s.SpringSplasher.class)); + cards.add(new SetCardInfo("Stagecoach Security", 30, Rarity.COMMON, mage.cards.s.StagecoachSecurity.class)); + cards.add(new SetCardInfo("Steer Clear", 31, Rarity.COMMON, mage.cards.s.SteerClear.class)); + cards.add(new SetCardInfo("Step Between Worlds", 321, Rarity.RARE, mage.cards.s.StepBetweenWorlds.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Step Between Worlds", 70, Rarity.RARE, mage.cards.s.StepBetweenWorlds.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sterling Hound", 249, Rarity.COMMON, mage.cards.s.SterlingHound.class)); + cards.add(new SetCardInfo("Sterling Keykeeper", 32, Rarity.COMMON, mage.cards.s.SterlingKeykeeper.class)); + cards.add(new SetCardInfo("Sterling Supplier", 33, Rarity.COMMON, mage.cards.s.SterlingSupplier.class)); + cards.add(new SetCardInfo("Stingerback Terror", 147, Rarity.RARE, mage.cards.s.StingerbackTerror.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Stingerback Terror", 336, Rarity.RARE, mage.cards.s.StingerbackTerror.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Stoic Sphinx", 322, Rarity.RARE, mage.cards.s.StoicSphinx.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Stoic Sphinx", 71, Rarity.RARE, mage.cards.s.StoicSphinx.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Stop Cold", 72, Rarity.COMMON, mage.cards.s.StopCold.class)); + cards.add(new SetCardInfo("Stubborn Burrowfiend", 184, Rarity.UNCOMMON, mage.cards.s.StubbornBurrowfiend.class)); + 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("Taii Wakeen, Perfect Shot", 234, Rarity.RARE, mage.cards.t.TaiiWakeenPerfectShot.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Taii Wakeen, Perfect Shot", 365, Rarity.RARE, mage.cards.t.TaiiWakeenPerfectShot.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Take Up the Shield", 34, Rarity.COMMON, mage.cards.t.TakeUpTheShield.class)); + cards.add(new SetCardInfo("Take for a Ride", 148, Rarity.UNCOMMON, mage.cards.t.TakeForARide.class)); + cards.add(new SetCardInfo("Take the Fall", 73, Rarity.COMMON, mage.cards.t.TakeTheFall.class)); + cards.add(new SetCardInfo("Terror of the Peaks", 149, Rarity.MYTHIC, mage.cards.t.TerrorOfThePeaks.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Terror of the Peaks", 337, Rarity.MYTHIC, mage.cards.t.TerrorOfThePeaks.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Gitrog, Ravenous Ride", 206, Rarity.MYTHIC, mage.cards.t.TheGitrogRavenousRide.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Gitrog, Ravenous Ride", 352, Rarity.MYTHIC, mage.cards.t.TheGitrogRavenousRide.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Key to the Vault", 320, Rarity.RARE, mage.cards.t.TheKeyToTheVault.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Key to the Vault", 373, Rarity.RARE, mage.cards.t.TheKeyToTheVault.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Key to the Vault", 54, Rarity.RARE, mage.cards.t.TheKeyToTheVault.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("This Town Ain't Big Enough", 74, Rarity.UNCOMMON, mage.cards.t.ThisTownAintBigEnough.class)); + cards.add(new SetCardInfo("Three Steps Ahead", 323, Rarity.RARE, mage.cards.t.ThreeStepsAhead.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Three Steps Ahead", 75, Rarity.RARE, mage.cards.t.ThreeStepsAhead.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Throw from the Saddle", 185, Rarity.COMMON, mage.cards.t.ThrowFromTheSaddle.class)); + cards.add(new SetCardInfo("Thunder Lasso", 35, Rarity.UNCOMMON, mage.cards.t.ThunderLasso.class)); + cards.add(new SetCardInfo("Thunder Salvo", 150, Rarity.COMMON, mage.cards.t.ThunderSalvo.class)); + cards.add(new SetCardInfo("Tinybones Joins Up", 108, Rarity.RARE, mage.cards.t.TinybonesJoinsUp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tinybones Joins Up", 328, Rarity.RARE, mage.cards.t.TinybonesJoinsUp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tinybones, the Pickpocket", 109, Rarity.MYTHIC, mage.cards.t.TinybonesThePickpocket.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tinybones, the Pickpocket", 290, Rarity.MYTHIC, mage.cards.t.TinybonesThePickpocket.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tomb Trawler", 250, Rarity.UNCOMMON, mage.cards.t.TombTrawler.class)); + cards.add(new SetCardInfo("Trained Arynx", 36, Rarity.COMMON, mage.cards.t.TrainedArynx.class)); + cards.add(new SetCardInfo("Trash the Town", 186, Rarity.UNCOMMON, mage.cards.t.TrashTheTown.class)); + cards.add(new SetCardInfo("Treasure Dredger", 110, Rarity.UNCOMMON, mage.cards.t.TreasureDredger.class)); + cards.add(new SetCardInfo("Trick Shot", 151, Rarity.COMMON, mage.cards.t.TrickShot.class)); + cards.add(new SetCardInfo("Tumbleweed Rising", 187, Rarity.COMMON, mage.cards.t.TumbleweedRising.class)); + cards.add(new SetCardInfo("Unfortunate Accident", 111, Rarity.UNCOMMON, mage.cards.u.UnfortunateAccident.class)); + cards.add(new SetCardInfo("Unscrupulous Contractor", 112, Rarity.UNCOMMON, mage.cards.u.UnscrupulousContractor.class)); + cards.add(new SetCardInfo("Vadmir, New Blood", 113, Rarity.RARE, mage.cards.v.VadmirNewBlood.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vadmir, New Blood", 329, Rarity.RARE, mage.cards.v.VadmirNewBlood.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vault Plunderer", 114, Rarity.COMMON, mage.cards.v.VaultPlunderer.class)); + cards.add(new SetCardInfo("Vengeful Townsfolk", 37, Rarity.COMMON, mage.cards.v.VengefulTownsfolk.class)); + cards.add(new SetCardInfo("Vial Smasher, Gleeful Grenadier", 235, Rarity.UNCOMMON, mage.cards.v.VialSmasherGleefulGrenadier.class)); + cards.add(new SetCardInfo("Visage Bandit", 76, Rarity.UNCOMMON, mage.cards.v.VisageBandit.class)); + cards.add(new SetCardInfo("Voracious Varmint", 188, Rarity.COMMON, mage.cards.v.VoraciousVarmint.class)); + cards.add(new SetCardInfo("Vraska Joins Up", 236, Rarity.RARE, mage.cards.v.VraskaJoinsUp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vraska Joins Up", 366, Rarity.RARE, mage.cards.v.VraskaJoinsUp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vraska, the Silencer", 237, Rarity.MYTHIC, mage.cards.v.VraskaTheSilencer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vraska, the Silencer", 299, Rarity.MYTHIC, mage.cards.v.VraskaTheSilencer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Wanted Griffin", 38, Rarity.COMMON, mage.cards.w.WantedGriffin.class)); + cards.add(new SetCardInfo("Wrangler of the Damned", 238, Rarity.UNCOMMON, mage.cards.w.WranglerOfTheDamned.class)); + cards.add(new SetCardInfo("Wylie Duke, Atiin Hero", 239, Rarity.RARE, mage.cards.w.WylieDukeAtiinHero.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Wylie Duke, Atiin Hero", 367, Rarity.RARE, mage.cards.w.WylieDukeAtiinHero.class, NON_FULL_USE_VARIOUS)); + } + + private Set specialLands = new HashSet<>(Arrays.asList(251, 253, 255, 256, 257, 258, 259, 260, 261, 264)); + + // otp: 30 rare, 15 mythic, otj: 60, 20 + private static double ratioRareMythicOfOtpInFoilSlot = (30.0 * 2.0 + 15.0) / ((30.0 + 60.0) * 2.0 + (15.0 + 20.0)); + private static double ratioMythic = 20.0 / (20.0 + 60.0 * 2.0); + private static double ratioOTPMythic = 15.0 / (15.0 + 30.0 * 2.0); + // otp: 20, otj: 100 + private static double ratioUncommonOPTInFoilSlot = 20.0 / (20.0 + 100.0); + + @Override + public List tryBooster() { + // TODO: make part of this more generic, this is the first try at a play booster generation. + // We start by deciding the various slots. + // Land Slot: 1/2 chance for a basic, 1/2 chance for a nonbasic in the special list + int basicLand = 0; + int nonbasicLand = 0; + { + if (RandomUtil.nextDouble() <= 0.5) { + basicLand++; + } else { + nonbasicLand++; + } + } + + // 1 slot is guarantee opt. + int otpUncommon = 0; + int otpRareOrMythic = 0; + { + double rollOtp = RandomUtil.nextDouble(); + if (rollOtp >= 1.0 / 3.0) { // know probability of 2/3 to have an uncommon. + otpUncommon++; + } else { + otpRareOrMythic++; + } + } + + // 8 slots have guarantee rarity + int rareOrMythic = 1; + int uncommon = 3; + int common = 5; + + // 1 slot is 1/64 chance to be spg, and 1/5 - 1/64 to be otp, 4/5 to be common + int spg = 0; + int big = 0; + { + double rollBig = RandomUtil.nextDouble(); + if (rollBig <= 1.0 / 64.0) { // know probability of 1/64 to be spg + spg++; + } else if (rollBig <= 1.0 / 5.0) { + big++; + } else { + common++; + } + } + + // 1 slot is a wildcard, with 1/12 to be r/m as a known info. + // MISSING INFO: relative chance of C/U in that slot. Let's assume 3/12 uncommon and 8/12 common. + // MISSING INFO: what about the special common lands? do they count here? + { + double rollWildcard = RandomUtil.nextDouble(); + if (rollWildcard <= 1.0 / 12.0) { + rareOrMythic++; + } else if (rollWildcard <= 4.0 / 12.0) { + uncommon++; + } else { + if (rollWildcard >= 1.0 - (8.0 / 12.0) * 10.0 / (10.0 + 81.0)) { + nonbasicLand++; + } else { + common++; + } + } + } + + // 1 slot is a (foil) wildcard that can be otp, we know nothing more here. + // MISSING INFO: all the following chances are made up + { + double rollFoilWildcard = RandomUtil.nextDouble(); + if (rollFoilWildcard <= 1.0 / 12.0) { + // Let's assume any of the rare among set + otp have same chance, that is twice the chance of mythic + if (rollFoilWildcard <= (1.0 / 12.0) * ratioRareMythicOfOtpInFoilSlot) { + otpRareOrMythic++; + } else { + rareOrMythic++; + } + } else if (rollFoilWildcard <= 4.0 / 12.0) { + if (rollFoilWildcard <= (1.0 / 12.0) + (3.0 / 12.0) * ratioUncommonOPTInFoilSlot) { + otpUncommon++; + } else { + uncommon++; + } + } else { + common++; + } + } + + /* + int total = rareOrMythic + uncommon + common + nonbasicLand + basicLand + otpRareOrMythic + otpUncommon + big + spg; + System.out.println( + "Total" + total + + "R" + rareOrMythic + " U" + uncommon + " C" + common + + " SL" + nonbasicLand + " B" + basicLand + + " OTP-R" + otpRareOrMythic + " OPT-U" + otpUncommon + + " BIG" + big + " SPG" + spg + ); + */ + + // The booster we are building + List booster = new ArrayList<>(); + + List list_OTJ_C_And_SL = + getCardsByRarity(Rarity.COMMON).stream() + .filter(info -> info.getCardNumberAsInt() <= maxCardNumberInBooster) + .collect(Collectors.toList()); + List list_OTJ_C = // All commons, minus the special lands + list_OTJ_C_And_SL.stream() + .filter(info -> !(specialLands.contains(info.getCardNumberAsInt()))) + .collect(Collectors.toList()); + List list_OTJ_SL = + list_OTJ_C_And_SL.stream() + .filter(info -> specialLands.contains(info.getCardNumberAsInt())) + .collect(Collectors.toList()); + List list_OTJ_U = + getCardsByRarity(Rarity.UNCOMMON) + .stream() + .filter(info -> info.getCardNumberAsInt() <= maxCardNumberInBooster) + .collect(Collectors.toList()); + List list_OTJ_R = + getCardsByRarity(Rarity.RARE) + .stream() + .filter(info -> info.getCardNumberAsInt() <= maxCardNumberInBooster) + .collect(Collectors.toList()); + List list_OTJ_M = + getCardsByRarity(Rarity.MYTHIC) + .stream() + .filter(info -> info.getCardNumberAsInt() <= maxCardNumberInBooster) + .collect(Collectors.toList()); + List list_OTJ_Basic = + getCardsByRarity(Rarity.LAND) + .stream() + .filter(info -> info.getCardNumberAsInt() <= maxCardNumberInBooster) + .collect(Collectors.toList()); + List list_OTP_U = + BreakingNews.getInstance().getCardsByRarity(Rarity.UNCOMMON) + .stream() + .filter(info -> info.getCardNumberAsInt() <= 65) + .collect(Collectors.toList()); + List list_OTP_R = + BreakingNews.getInstance().getCardsByRarity(Rarity.RARE) + .stream() + .filter(info -> info.getCardNumberAsInt() <= 65) + .collect(Collectors.toList()); + List list_OTP_M = + BreakingNews.getInstance().getCardsByRarity(Rarity.MYTHIC) + .stream() + .filter(info -> info.getCardNumberAsInt() <= 65) + .collect(Collectors.toList()); + List list_BIG = + TheBigScore.getInstance().getCardsByRarity(Rarity.MYTHIC) + .stream() + .filter(info -> info.getCardNumberAsInt() <= 30) + .collect(Collectors.toList()); + List list_SPG = + SpecialGuests.getInstance().getCardsByRarity(Rarity.MYTHIC) + .stream() + .filter(info -> { + int cn = info.getCardNumberAsInt(); + return cn >= 29 && cn <= 38; + }) + .collect(Collectors.toList()); + + for (int i = 0; i < spg; i++) { + addToBooster(booster, list_SPG); + } + for (int i = 0; i < big; i++) { + addToBooster(booster, list_BIG); + } + for (int i = 0; i < rareOrMythic; i++) { + if (RandomUtil.nextDouble() <= ratioMythic) { + addToBooster(booster, list_OTJ_M); + } else { + addToBooster(booster, list_OTJ_R); + } + } + for (int i = 0; i < otpRareOrMythic; i++) { + if (RandomUtil.nextDouble() <= ratioOTPMythic) { + addToBooster(booster, list_OTP_M); + } else { + addToBooster(booster, list_OTP_R); + } + } + for (int i = 0; i < otpUncommon; i++) { + addToBooster(booster, list_OTP_U); + } + for (int i = 0; i < uncommon; i++) { + addToBooster(booster, list_OTJ_U); + } + for (int i = 0; i < common; i++) { + addToBooster(booster, list_OTJ_C); + } + for (int i = 0; i < nonbasicLand; i++) { + addToBooster(booster, list_OTJ_SL); + } + for (int i = 0; i < basicLand; i++) { + addToBooster(booster, list_OTJ_Basic); + } + + return booster; } } diff --git a/Mage.Sets/src/mage/sets/OutlawsOfThunderJunctionCommander.java b/Mage.Sets/src/mage/sets/OutlawsOfThunderJunctionCommander.java new file mode 100644 index 00000000000..93f9802a138 --- /dev/null +++ b/Mage.Sets/src/mage/sets/OutlawsOfThunderJunctionCommander.java @@ -0,0 +1,311 @@ +package mage.sets; + +import mage.cards.ExpansionSet; +import mage.constants.Rarity; +import mage.constants.SetType; + +/** + * @author TheElk801 + */ +public final class OutlawsOfThunderJunctionCommander extends ExpansionSet { + + private static final OutlawsOfThunderJunctionCommander instance = new OutlawsOfThunderJunctionCommander(); + + public static OutlawsOfThunderJunctionCommander getInstance() { + return instance; + } + + private OutlawsOfThunderJunctionCommander() { + super("Outlaws of Thunder Junction Commander", "OTC", ExpansionSet.buildDate(2024, 4, 19), SetType.SUPPLEMENTAL); + this.hasBasicLands = false; + + cards.add(new SetCardInfo("Academy Manufactor", 251, Rarity.RARE, mage.cards.a.AcademyManufactor.class)); + cards.add(new SetCardInfo("Access Tunnel", 270, Rarity.UNCOMMON, mage.cards.a.AccessTunnel.class)); + cards.add(new SetCardInfo("Aetherborn Marauder", 125, Rarity.UNCOMMON, mage.cards.a.AetherbornMarauder.class)); + cards.add(new SetCardInfo("Ancient Greenwarden", 186, Rarity.MYTHIC, mage.cards.a.AncientGreenwarden.class)); + cards.add(new SetCardInfo("Angel of Indemnity", 9, Rarity.RARE, mage.cards.a.AngelOfIndemnity.class)); + cards.add(new SetCardInfo("Angel of the Ruins", 78, Rarity.UNCOMMON, mage.cards.a.AngelOfTheRuins.class)); + cards.add(new SetCardInfo("Angelic Sell-Sword", 10, Rarity.RARE, mage.cards.a.AngelicSellSword.class)); + cards.add(new SetCardInfo("Angrath's Marauders", 153, Rarity.RARE, mage.cards.a.AngrathsMarauders.class)); + cards.add(new SetCardInfo("Arcane Bombardment", 154, Rarity.MYTHIC, mage.cards.a.ArcaneBombardment.class)); + cards.add(new SetCardInfo("Arcane Denial", 89, Rarity.COMMON, mage.cards.a.ArcaneDenial.class)); + cards.add(new SetCardInfo("Arcane Heist", 13, Rarity.RARE, mage.cards.a.ArcaneHeist.class)); + cards.add(new SetCardInfo("Arcane Signet", 252, Rarity.COMMON, mage.cards.a.ArcaneSignet.class)); + cards.add(new SetCardInfo("Archmage Emeritus", 90, Rarity.RARE, mage.cards.a.ArchmageEmeritus.class)); + cards.add(new SetCardInfo("Avenger of Zendikar", 187, Rarity.MYTHIC, mage.cards.a.AvengerOfZendikar.class)); + cards.add(new SetCardInfo("Back in Town", 18, Rarity.RARE, mage.cards.b.BackInTown.class)); + cards.add(new SetCardInfo("Baleful Mastery", 126, Rarity.RARE, mage.cards.b.BalefulMastery.class)); + cards.add(new SetCardInfo("Baleful Strix", 215, Rarity.RARE, mage.cards.b.BalefulStrix.class)); + cards.add(new SetCardInfo("Baral's Expertise", 91, Rarity.RARE, mage.cards.b.BaralsExpertise.class)); + cards.add(new SetCardInfo("Battlefield Forge", 271, Rarity.RARE, mage.cards.b.BattlefieldForge.class)); + cards.add(new SetCardInfo("Big Score", 155, Rarity.COMMON, mage.cards.b.BigScore.class)); + cards.add(new SetCardInfo("Bitter Reunion", 156, Rarity.COMMON, mage.cards.b.BitterReunion.class)); + cards.add(new SetCardInfo("Blackcleave Cliffs", 272, Rarity.RARE, mage.cards.b.BlackcleaveCliffs.class)); + cards.add(new SetCardInfo("Bladegriff Prototype", 253, Rarity.RARE, mage.cards.b.BladegriffPrototype.class)); + cards.add(new SetCardInfo("Bloodthirsty Adversary", 157, Rarity.MYTHIC, mage.cards.b.BloodthirstyAdversary.class)); + cards.add(new SetCardInfo("Bojuka Bog", 273, Rarity.COMMON, mage.cards.b.BojukaBog.class)); + cards.add(new SetCardInfo("Bonders' Enclave", 274, Rarity.RARE, mage.cards.b.BondersEnclave.class)); + cards.add(new SetCardInfo("Boros Charm", 216, Rarity.UNCOMMON, mage.cards.b.BorosCharm.class)); + cards.add(new SetCardInfo("Brainstealer Dragon", 127, Rarity.RARE, mage.cards.b.BrainstealerDragon.class)); + cards.add(new SetCardInfo("Breena, the Demagogue", 217, Rarity.MYTHIC, mage.cards.b.BreenaTheDemagogue.class)); + cards.add(new SetCardInfo("Cactus Preserve", 40, Rarity.RARE, mage.cards.c.CactusPreserve.class)); + cards.add(new SetCardInfo("Canyon Slough", 275, Rarity.RARE, mage.cards.c.CanyonSlough.class)); + cards.add(new SetCardInfo("Captain Lannery Storm", 158, Rarity.RARE, mage.cards.c.CaptainLanneryStorm.class)); + cards.add(new SetCardInfo("Captivating Crew", 159, Rarity.RARE, mage.cards.c.CaptivatingCrew.class)); + cards.add(new SetCardInfo("Cascade Bluffs", 276, Rarity.RARE, mage.cards.c.CascadeBluffs.class)); + cards.add(new SetCardInfo("Cataclysmic Prospecting", 24, Rarity.RARE, mage.cards.c.CataclysmicProspecting.class)); + cards.add(new SetCardInfo("Caves of Koilos", 277, Rarity.RARE, mage.cards.c.CavesOfKoilos.class)); + cards.add(new SetCardInfo("Cazur, Ruthless Stalker", 188, Rarity.MYTHIC, mage.cards.c.CazurRuthlessStalker.class)); + cards.add(new SetCardInfo("Changeling Outcast", 128, Rarity.COMMON, mage.cards.c.ChangelingOutcast.class)); + cards.add(new SetCardInfo("Chaos Wand", 254, Rarity.RARE, mage.cards.c.ChaosWand.class)); + cards.add(new SetCardInfo("Chaos Warp", 160, Rarity.RARE, mage.cards.c.ChaosWarp.class)); + cards.add(new SetCardInfo("Charred Graverobber", 19, Rarity.RARE, mage.cards.c.CharredGraverobber.class)); + cards.add(new SetCardInfo("Chromatic Lantern", 255, Rarity.RARE, mage.cards.c.ChromaticLantern.class)); + cards.add(new SetCardInfo("Clifftop Retreat", 278, Rarity.RARE, mage.cards.c.ClifftopRetreat.class)); + cards.add(new SetCardInfo("Cold-Eyed Selkie", 218, Rarity.RARE, mage.cards.c.ColdEyedSelkie.class)); + cards.add(new SetCardInfo("Command Beacon", 279, Rarity.RARE, mage.cards.c.CommandBeacon.class)); + cards.add(new SetCardInfo("Command Tower", 280, Rarity.COMMON, mage.cards.c.CommandTower.class)); + cards.add(new SetCardInfo("Council's Judgment", 79, Rarity.RARE, mage.cards.c.CouncilsJudgment.class)); + cards.add(new SetCardInfo("Crackling Spellslinger", 25, Rarity.RARE, mage.cards.c.CracklingSpellslinger.class)); + cards.add(new SetCardInfo("Crawling Sensation", 189, Rarity.UNCOMMON, mage.cards.c.CrawlingSensation.class)); + cards.add(new SetCardInfo("Culling Ritual", 219, Rarity.RARE, mage.cards.c.CullingRitual.class)); + cards.add(new SetCardInfo("Cunning Rhetoric", 129, Rarity.RARE, mage.cards.c.CunningRhetoric.class)); + cards.add(new SetCardInfo("Curse of the Swine", 92, Rarity.RARE, mage.cards.c.CurseOfTheSwine.class)); + cards.add(new SetCardInfo("Cursed Mirror", 161, Rarity.RARE, mage.cards.c.CursedMirror.class)); + cards.add(new SetCardInfo("Curtains' Call", 130, Rarity.RARE, mage.cards.c.CurtainsCall.class)); + cards.add(new SetCardInfo("Darkslick Shores", 281, Rarity.RARE, mage.cards.d.DarkslickShores.class)); + cards.add(new SetCardInfo("Darksteel Ingot", 256, Rarity.UNCOMMON, mage.cards.d.DarksteelIngot.class)); + cards.add(new SetCardInfo("Darkwater Catacombs", 282, Rarity.RARE, mage.cards.d.DarkwaterCatacombs.class)); + cards.add(new SetCardInfo("Dazzling Sphinx", 93, Rarity.RARE, mage.cards.d.DazzlingSphinx.class)); + cards.add(new SetCardInfo("Deadly Dispute", 131, Rarity.COMMON, mage.cards.d.DeadlyDispute.class)); + cards.add(new SetCardInfo("Decimate", 220, Rarity.RARE, mage.cards.d.Decimate.class)); + cards.add(new SetCardInfo("Deep Analysis", 94, Rarity.COMMON, mage.cards.d.DeepAnalysis.class)); + cards.add(new SetCardInfo("Demolition Field", 283, Rarity.UNCOMMON, mage.cards.d.DemolitionField.class)); + cards.add(new SetCardInfo("Descend upon the Sinful", 80, Rarity.MYTHIC, mage.cards.d.DescendUponTheSinful.class)); + cards.add(new SetCardInfo("Desert of the Fervent", 284, Rarity.COMMON, mage.cards.d.DesertOfTheFervent.class)); + cards.add(new SetCardInfo("Desert of the Indomitable", 285, Rarity.COMMON, mage.cards.d.DesertOfTheIndomitable.class)); + cards.add(new SetCardInfo("Desert of the True", 286, Rarity.COMMON, mage.cards.d.DesertOfTheTrue.class)); + cards.add(new SetCardInfo("Desolate Mire", 287, Rarity.RARE, mage.cards.d.DesolateMire.class)); + cards.add(new SetCardInfo("Dig Through Time", 95, Rarity.RARE, mage.cards.d.DigThroughTime.class)); + cards.add(new SetCardInfo("Diluvian Primordial", 96, Rarity.RARE, mage.cards.d.DiluvianPrimordial.class)); + cards.add(new SetCardInfo("Dimir Aqueduct", 288, Rarity.COMMON, mage.cards.d.DimirAqueduct.class)); + cards.add(new SetCardInfo("Dire Fleet Daredevil", 162, Rarity.RARE, mage.cards.d.DireFleetDaredevil.class)); + cards.add(new SetCardInfo("Dire Fleet Ravager", 132, Rarity.MYTHIC, mage.cards.d.DireFleetRavager.class)); + cards.add(new SetCardInfo("Dragonskull Summit", 289, Rarity.RARE, mage.cards.d.DragonskullSummit.class)); + cards.add(new SetCardInfo("Drowned Catacomb", 290, Rarity.RARE, mage.cards.d.DrownedCatacomb.class)); + cards.add(new SetCardInfo("Dune Chanter", 31, Rarity.RARE, mage.cards.d.DuneChanter.class)); + cards.add(new SetCardInfo("Dunes of the Dead", 291, Rarity.UNCOMMON, mage.cards.d.DunesOfTheDead.class)); + cards.add(new SetCardInfo("Eccentric Farmer", 190, Rarity.COMMON, mage.cards.e.EccentricFarmer.class)); + cards.add(new SetCardInfo("Edric, Spymaster of Trest", 221, Rarity.RARE, mage.cards.e.EdricSpymasterOfTrest.class)); + cards.add(new SetCardInfo("Electric Revelation", 163, Rarity.COMMON, mage.cards.e.ElectricRevelation.class)); + cards.add(new SetCardInfo("Electrostatic Field", 164, Rarity.UNCOMMON, mage.cards.e.ElectrostaticField.class)); + cards.add(new SetCardInfo("Elemental Eruption", 27, Rarity.RARE, mage.cards.e.ElementalEruption.class)); + cards.add(new SetCardInfo("Elvish Rejuvenator", 191, Rarity.COMMON, mage.cards.e.ElvishRejuvenator.class)); + cards.add(new SetCardInfo("Embrace the Unknown", 28, Rarity.RARE, mage.cards.e.EmbraceTheUnknown.class)); + cards.add(new SetCardInfo("Epic Experiment", 222, Rarity.MYTHIC, mage.cards.e.EpicExperiment.class)); + cards.add(new SetCardInfo("Eris, Roar of the Storm", 5, Rarity.MYTHIC, mage.cards.e.ErisRoarOfTheStorm.class)); + cards.add(new SetCardInfo("Escape to the Wilds", 223, Rarity.RARE, mage.cards.e.EscapeToTheWilds.class)); + cards.add(new SetCardInfo("Evolving Wilds", 292, Rarity.COMMON, mage.cards.e.EvolvingWilds.class)); + cards.add(new SetCardInfo("Exotic Orchard", 293, Rarity.RARE, mage.cards.e.ExoticOrchard.class)); + cards.add(new SetCardInfo("Explore", 192, Rarity.COMMON, mage.cards.e.Explore.class)); + cards.add(new SetCardInfo("Expressive Iteration", 224, Rarity.UNCOMMON, mage.cards.e.ExpressiveIteration.class)); + cards.add(new SetCardInfo("Extract Brain", 225, Rarity.RARE, mage.cards.e.ExtractBrain.class)); + cards.add(new SetCardInfo("Fain, the Broker", 133, Rarity.RARE, mage.cards.f.FainTheBroker.class)); + cards.add(new SetCardInfo("Faithless Looting", 165, Rarity.COMMON, mage.cards.f.FaithlessLooting.class)); + cards.add(new SetCardInfo("Fallen Shinobi", 226, Rarity.RARE, mage.cards.f.FallenShinobi.class)); + cards.add(new SetCardInfo("Feed the Swarm", 134, Rarity.COMMON, mage.cards.f.FeedTheSwarm.class)); + cards.add(new SetCardInfo("Fellwar Stone", 257, Rarity.UNCOMMON, mage.cards.f.FellwarStone.class)); + cards.add(new SetCardInfo("Ferrous Lake", 294, Rarity.RARE, mage.cards.f.FerrousLake.class)); + cards.add(new SetCardInfo("Fetid Heath", 295, Rarity.RARE, mage.cards.f.FetidHeath.class)); + cards.add(new SetCardInfo("Fetid Pools", 296, Rarity.RARE, mage.cards.f.FetidPools.class)); + cards.add(new SetCardInfo("Finale of Promise", 166, Rarity.MYTHIC, mage.cards.f.FinaleOfPromise.class)); + cards.add(new SetCardInfo("Finale of Revelation", 97, Rarity.MYTHIC, mage.cards.f.FinaleOfRevelation.class)); + cards.add(new SetCardInfo("Flooded Grove", 297, Rarity.RARE, mage.cards.f.FloodedGrove.class)); + cards.add(new SetCardInfo("Frostboil Snarl", 298, Rarity.RARE, mage.cards.f.FrostboilSnarl.class)); + cards.add(new SetCardInfo("Galvanic Iteration", 227, Rarity.RARE, mage.cards.g.GalvanicIteration.class)); + cards.add(new SetCardInfo("Genesis Hydra", 193, Rarity.RARE, mage.cards.g.GenesisHydra.class)); + cards.add(new SetCardInfo("Ghostly Pilferer", 98, Rarity.RARE, mage.cards.g.GhostlyPilferer.class)); + cards.add(new SetCardInfo("Glittering Stockpile", 167, Rarity.UNCOMMON, mage.cards.g.GlitteringStockpile.class)); + cards.add(new SetCardInfo("Goblin Electromancer", 228, Rarity.COMMON, mage.cards.g.GoblinElectromancer.class)); + cards.add(new SetCardInfo("Gonti, Lord of Luxury", 135, Rarity.RARE, mage.cards.g.GontiLordOfLuxury.class)); + cards.add(new SetCardInfo("Grenzo, Havoc Raiser", 168, Rarity.RARE, mage.cards.g.GrenzoHavocRaiser.class)); + cards.add(new SetCardInfo("Guttersnipe", 169, Rarity.COMMON, mage.cards.g.Guttersnipe.class)); + cards.add(new SetCardInfo("Harrow", 194, Rarity.COMMON, mage.cards.h.Harrow.class)); + cards.add(new SetCardInfo("Hashep Oasis", 299, Rarity.UNCOMMON, mage.cards.h.HashepOasis.class)); + cards.add(new SetCardInfo("Haughty Djinn", 99, Rarity.RARE, mage.cards.h.HaughtyDjinn.class)); + cards.add(new SetCardInfo("Hazezon, Shaper of Sand", 229, Rarity.RARE, mage.cards.h.HazezonShaperOfSand.class)); + cards.add(new SetCardInfo("Heaven // Earth", 230, Rarity.RARE, mage.cards.h.HeavenEarth.class)); + cards.add(new SetCardInfo("Heliod's Intervention", 81, Rarity.RARE, mage.cards.h.HeliodsIntervention.class)); + cards.add(new SetCardInfo("Hex", 136, Rarity.RARE, mage.cards.h.Hex.class)); + cards.add(new SetCardInfo("Hinterland Harbor", 300, Rarity.RARE, mage.cards.h.HinterlandHarbor.class)); + cards.add(new SetCardInfo("Hostage Taker", 231, Rarity.RARE, mage.cards.h.HostageTaker.class)); + cards.add(new SetCardInfo("Hour of Promise", 195, Rarity.RARE, mage.cards.h.HourOfPromise.class)); + cards.add(new SetCardInfo("Humble Defector", 170, Rarity.UNCOMMON, mage.cards.h.HumbleDefector.class)); + cards.add(new SetCardInfo("Idol of Oblivion", 258, Rarity.RARE, mage.cards.i.IdolOfOblivion.class)); + cards.add(new SetCardInfo("Impulsive Pilferer", 171, Rarity.COMMON, mage.cards.i.ImpulsivePilferer.class)); + cards.add(new SetCardInfo("Isolated Chapel", 301, Rarity.RARE, mage.cards.i.IsolatedChapel.class)); + cards.add(new SetCardInfo("Izzet Boilerworks", 302, Rarity.UNCOMMON, mage.cards.i.IzzetBoilerworks.class)); + cards.add(new SetCardInfo("Izzet Signet", 259, Rarity.COMMON, mage.cards.i.IzzetSignet.class)); + cards.add(new SetCardInfo("Jungle Shrine", 303, Rarity.UNCOMMON, mage.cards.j.JungleShrine.class)); + cards.add(new SetCardInfo("Kamber, the Plunderer", 137, Rarity.RARE, mage.cards.k.KamberThePlunderer.class)); + cards.add(new SetCardInfo("Kaza, Roil Chaser", 232, Rarity.RARE, mage.cards.k.KazaRoilChaser.class)); + cards.add(new SetCardInfo("Kirri, Talented Sprout", 7, Rarity.MYTHIC, mage.cards.k.KirriTalentedSprout.class)); + cards.add(new SetCardInfo("Kodama's Reach", 196, Rarity.COMMON, mage.cards.k.KodamasReach.class)); + cards.add(new SetCardInfo("Krosan Verge", 304, Rarity.UNCOMMON, mage.cards.k.KrosanVerge.class)); + cards.add(new SetCardInfo("Laurine, the Diversion", 172, Rarity.RARE, mage.cards.l.LaurineTheDiversion.class)); + cards.add(new SetCardInfo("Leyline Dowser", 39, Rarity.RARE, mage.cards.l.LeylineDowser.class)); + cards.add(new SetCardInfo("Life Insurance", 233, Rarity.RARE, mage.cards.l.LifeInsurance.class)); + cards.add(new SetCardInfo("Lightning Greaves", 260, Rarity.UNCOMMON, mage.cards.l.LightningGreaves.class)); + cards.add(new SetCardInfo("Llanowar Wastes", 305, Rarity.RARE, mage.cards.l.LlanowarWastes.class)); + cards.add(new SetCardInfo("Lock and Load", 15, Rarity.RARE, mage.cards.l.LockAndLoad.class)); + cards.add(new SetCardInfo("Magmatic Insight", 173, Rarity.UNCOMMON, mage.cards.m.MagmaticInsight.class)); + cards.add(new SetCardInfo("Mari, the Killing Quill", 138, Rarity.RARE, mage.cards.m.MariTheKillingQuill.class)); + cards.add(new SetCardInfo("Marshal's Anthem", 82, Rarity.RARE, mage.cards.m.MarshalsAnthem.class)); + cards.add(new SetCardInfo("Marshland Bloodcaster", 139, Rarity.RARE, mage.cards.m.MarshlandBloodcaster.class)); + cards.add(new SetCardInfo("Mass Mutiny", 174, Rarity.RARE, mage.cards.m.MassMutiny.class)); + cards.add(new SetCardInfo("Massacre Girl", 140, Rarity.RARE, mage.cards.m.MassacreGirl.class)); + cards.add(new SetCardInfo("Midnight Clock", 100, Rarity.RARE, mage.cards.m.MidnightClock.class)); + cards.add(new SetCardInfo("Mind's Dilation", 101, Rarity.MYTHIC, mage.cards.m.MindsDilation.class)); + cards.add(new SetCardInfo("Mirror Entity", 83, Rarity.RARE, mage.cards.m.MirrorEntity.class)); + cards.add(new SetCardInfo("Misfortune Teller", 141, Rarity.RARE, mage.cards.m.MisfortuneTeller.class)); + cards.add(new SetCardInfo("Mistmeadow Skulk", 84, Rarity.UNCOMMON, mage.cards.m.MistmeadowSkulk.class)); + cards.add(new SetCardInfo("Mizzix's Mastery", 175, Rarity.RARE, mage.cards.m.MizzixsMastery.class)); + cards.add(new SetCardInfo("Morbid Opportunist", 142, Rarity.UNCOMMON, mage.cards.m.MorbidOpportunist.class)); + cards.add(new SetCardInfo("Murmuring Mystic", 102, Rarity.UNCOMMON, mage.cards.m.MurmuringMystic.class)); + cards.add(new SetCardInfo("Nantuko Cultivator", 198, Rarity.UNCOMMON, mage.cards.n.NantukoCultivator.class)); + cards.add(new SetCardInfo("Nashi, Moon Sage's Scion", 143, Rarity.MYTHIC, mage.cards.n.NashiMoonSagesScion.class)); + cards.add(new SetCardInfo("Nesting Dragon", 176, Rarity.RARE, mage.cards.n.NestingDragon.class)); + cards.add(new SetCardInfo("Nighthawk Scavenger", 144, Rarity.RARE, mage.cards.n.NighthawkScavenger.class)); + cards.add(new SetCardInfo("Niv-Mizzet, Parun", 235, Rarity.RARE, mage.cards.n.NivMizzetParun.class)); + cards.add(new SetCardInfo("Nomad Outpost", 306, Rarity.UNCOMMON, mage.cards.n.NomadOutpost.class)); + cards.add(new SetCardInfo("Oblivion Sower", 77, Rarity.MYTHIC, mage.cards.o.OblivionSower.class)); + cards.add(new SetCardInfo("Octavia, Living Thesis", 103, Rarity.RARE, mage.cards.o.OctaviaLivingThesis.class)); + cards.add(new SetCardInfo("Ogre Slumlord", 145, Rarity.RARE, mage.cards.o.OgreSlumlord.class)); + cards.add(new SetCardInfo("Ohran Frostfang", 199, Rarity.RARE, mage.cards.o.OhranFrostfang.class)); + cards.add(new SetCardInfo("Omnath, Locus of Rage", 236, Rarity.MYTHIC, mage.cards.o.OmnathLocusOfRage.class)); + cards.add(new SetCardInfo("Opt", 104, Rarity.COMMON, mage.cards.o.Opt.class)); + cards.add(new SetCardInfo("Opulent Palace", 307, Rarity.UNCOMMON, mage.cards.o.OpulentPalace.class)); + cards.add(new SetCardInfo("Oracle of Mul Daya", 200, Rarity.RARE, mage.cards.o.OracleOfMulDaya.class)); + cards.add(new SetCardInfo("Orzhov Signet", 261, Rarity.COMMON, mage.cards.o.OrzhovSignet.class)); + cards.add(new SetCardInfo("Overflowing Basin", 308, Rarity.RARE, mage.cards.o.OverflowingBasin.class)); + cards.add(new SetCardInfo("Painful Truths", 146, Rarity.RARE, mage.cards.p.PainfulTruths.class)); + cards.add(new SetCardInfo("Painted Bluffs", 309, Rarity.COMMON, mage.cards.p.PaintedBluffs.class)); + cards.add(new SetCardInfo("Path of Ancestry", 310, Rarity.COMMON, mage.cards.p.PathOfAncestry.class)); + cards.add(new SetCardInfo("Path to Exile", 85, Rarity.UNCOMMON, mage.cards.p.PathToExile.class)); + cards.add(new SetCardInfo("Perennial Behemoth", 262, Rarity.RARE, mage.cards.p.PerennialBehemoth.class)); + cards.add(new SetCardInfo("Perpetual Timepiece", 263, Rarity.UNCOMMON, mage.cards.p.PerpetualTimepiece.class)); + cards.add(new SetCardInfo("Plasm Capture", 237, Rarity.RARE, mage.cards.p.PlasmCapture.class)); + cards.add(new SetCardInfo("Ponder", 105, Rarity.COMMON, mage.cards.p.Ponder.class)); + cards.add(new SetCardInfo("Pongify", 106, Rarity.UNCOMMON, mage.cards.p.Pongify.class)); + cards.add(new SetCardInfo("Predators' Hour", 147, Rarity.RARE, mage.cards.p.PredatorsHour.class)); + cards.add(new SetCardInfo("Preordain", 107, Rarity.COMMON, mage.cards.p.Preordain.class)); + cards.add(new SetCardInfo("Prismatic Lens", 264, Rarity.UNCOMMON, mage.cards.p.PrismaticLens.class)); + cards.add(new SetCardInfo("Propaganda", 108, Rarity.UNCOMMON, mage.cards.p.Propaganda.class)); + cards.add(new SetCardInfo("Pteramander", 109, Rarity.UNCOMMON, mage.cards.p.Pteramander.class)); + cards.add(new SetCardInfo("Putrefy", 238, Rarity.UNCOMMON, mage.cards.p.Putrefy.class)); + cards.add(new SetCardInfo("Queen Marchesa", 239, Rarity.RARE, mage.cards.q.QueenMarchesa.class)); + cards.add(new SetCardInfo("Radical Idea", 110, Rarity.COMMON, mage.cards.r.RadicalIdea.class)); + cards.add(new SetCardInfo("Rain of Riches", 177, Rarity.RARE, mage.cards.r.RainOfRiches.class)); + cards.add(new SetCardInfo("Rakdos Signet", 265, Rarity.UNCOMMON, mage.cards.r.RakdosSignet.class)); + cards.add(new SetCardInfo("Rampant Growth", 201, Rarity.COMMON, mage.cards.r.RampantGrowth.class)); + cards.add(new SetCardInfo("Ramunap Excavator", 202, Rarity.RARE, mage.cards.r.RamunapExcavator.class)); + cards.add(new SetCardInfo("Ramunap Ruins", 311, Rarity.UNCOMMON, mage.cards.r.RamunapRuins.class)); + cards.add(new SetCardInfo("Rankle, Master of Pranks", 148, Rarity.RARE, mage.cards.r.RankleMasterOfPranks.class)); + cards.add(new SetCardInfo("Reliquary Tower", 312, Rarity.UNCOMMON, mage.cards.r.ReliquaryTower.class)); + cards.add(new SetCardInfo("Return of the Wildspeaker", 203, Rarity.RARE, mage.cards.r.ReturnOfTheWildspeaker.class)); + cards.add(new SetCardInfo("Rogue's Passage", 313, Rarity.UNCOMMON, mage.cards.r.RoguesPassage.class)); + cards.add(new SetCardInfo("Rousing Refrain", 178, Rarity.RARE, mage.cards.r.RousingRefrain.class)); + cards.add(new SetCardInfo("Rugged Prairie", 314, Rarity.RARE, mage.cards.r.RuggedPrairie.class)); + cards.add(new SetCardInfo("Rumbleweed", 32, Rarity.RARE, mage.cards.r.Rumbleweed.class)); + cards.add(new SetCardInfo("Sage of the Beyond", 111, Rarity.RARE, mage.cards.s.SageOfTheBeyond.class)); + cards.add(new SetCardInfo("Sand Scout", 11, Rarity.RARE, mage.cards.s.SandScout.class)); + cards.add(new SetCardInfo("Satyr Wayfinder", 204, Rarity.COMMON, mage.cards.s.SatyrWayfinder.class)); + cards.add(new SetCardInfo("Savvy Trader", 33, Rarity.RARE, mage.cards.s.SavvyTrader.class)); + cards.add(new SetCardInfo("Scaretiller", 266, Rarity.COMMON, mage.cards.s.Scaretiller.class)); + cards.add(new SetCardInfo("Scattered Groves", 315, Rarity.RARE, mage.cards.s.ScatteredGroves.class)); + cards.add(new SetCardInfo("Scavenger Grounds", 316, Rarity.RARE, mage.cards.s.ScavengerGrounds.class)); + cards.add(new SetCardInfo("Scute Swarm", 205, Rarity.RARE, mage.cards.s.ScuteSwarm.class)); + cards.add(new SetCardInfo("Seize the Spotlight", 179, Rarity.RARE, mage.cards.s.SeizeTheSpotlight.class)); + cards.add(new SetCardInfo("Serum Visions", 112, Rarity.UNCOMMON, mage.cards.s.SerumVisions.class)); + cards.add(new SetCardInfo("Sevinne's Reclamation", 86, Rarity.RARE, mage.cards.s.SevinnesReclamation.class)); + cards.add(new SetCardInfo("Shadowblood Ridge", 317, Rarity.RARE, mage.cards.s.ShadowbloodRidge.class)); + cards.add(new SetCardInfo("Shadowmage Infiltrator", 240, Rarity.RARE, mage.cards.s.ShadowmageInfiltrator.class)); + cards.add(new SetCardInfo("Shark Typhoon", 113, Rarity.RARE, mage.cards.s.SharkTyphoon.class)); + cards.add(new SetCardInfo("Shefet Dunes", 318, Rarity.UNCOMMON, mage.cards.s.ShefetDunes.class)); + cards.add(new SetCardInfo("Sheltered Thicket", 319, Rarity.RARE, mage.cards.s.ShelteredThicket.class)); + cards.add(new SetCardInfo("Shiny Impetus", 180, Rarity.UNCOMMON, mage.cards.s.ShinyImpetus.class)); + cards.add(new SetCardInfo("Shivan Reef", 320, Rarity.RARE, mage.cards.s.ShivanReef.class)); + cards.add(new SetCardInfo("Silent-Blade Oni", 241, Rarity.RARE, mage.cards.s.SilentBladeOni.class)); + cards.add(new SetCardInfo("Silhana Ledgewalker", 206, Rarity.COMMON, mage.cards.s.SilhanaLedgewalker.class)); + cards.add(new SetCardInfo("Siphon Insight", 242, Rarity.RARE, mage.cards.s.SiphonInsight.class)); + cards.add(new SetCardInfo("Skullwinder", 207, Rarity.UNCOMMON, mage.cards.s.Skullwinder.class)); + cards.add(new SetCardInfo("Slither Blade", 114, Rarity.COMMON, mage.cards.s.SlitherBlade.class)); + cards.add(new SetCardInfo("Smirking Spelljacker", 16, Rarity.RARE, mage.cards.s.SmirkingSpelljacker.class)); + cards.add(new SetCardInfo("Smoldering Marsh", 321, Rarity.RARE, mage.cards.s.SmolderingMarsh.class)); + cards.add(new SetCardInfo("Sol Ring", 267, Rarity.UNCOMMON, mage.cards.s.SolRing.class)); + cards.add(new SetCardInfo("Springbloom Druid", 208, Rarity.COMMON, mage.cards.s.SpringbloomDruid.class)); + cards.add(new SetCardInfo("Stella Lee, Wild Card", 3, Rarity.MYTHIC, mage.cards.s.StellaLeeWildCard.class)); + cards.add(new SetCardInfo("Stolen Goods", 115, Rarity.RARE, mage.cards.s.StolenGoods.class)); + cards.add(new SetCardInfo("Storm-Kiln Artist", 181, Rarity.UNCOMMON, mage.cards.s.StormKilnArtist.class)); + cards.add(new SetCardInfo("Sulfur Falls", 322, Rarity.RARE, mage.cards.s.SulfurFalls.class)); + cards.add(new SetCardInfo("Sulfurous Springs", 323, Rarity.RARE, mage.cards.s.SulfurousSprings.class)); + cards.add(new SetCardInfo("Sun Titan", 87, Rarity.MYTHIC, mage.cards.s.SunTitan.class)); + cards.add(new SetCardInfo("Sunhome, Fortress of the Legion", 324, Rarity.UNCOMMON, mage.cards.s.SunhomeFortressOfTheLegion.class)); + cards.add(new SetCardInfo("Sunken Hollow", 325, Rarity.RARE, mage.cards.s.SunkenHollow.class)); + cards.add(new SetCardInfo("Sunscorched Divide", 326, Rarity.RARE, mage.cards.s.SunscorchedDivide.class)); + cards.add(new SetCardInfo("Swiftfoot Boots", 268, Rarity.UNCOMMON, mage.cards.s.SwiftfootBoots.class)); + cards.add(new SetCardInfo("Tainted Peak", 327, Rarity.UNCOMMON, mage.cards.t.TaintedPeak.class)); + cards.add(new SetCardInfo("Talrand, Sky Summoner", 116, Rarity.RARE, mage.cards.t.TalrandSkySummoner.class)); + cards.add(new SetCardInfo("Temple of Deceit", 328, Rarity.RARE, mage.cards.t.TempleOfDeceit.class)); + cards.add(new SetCardInfo("Temple of Epiphany", 329, Rarity.RARE, mage.cards.t.TempleOfEpiphany.class)); + cards.add(new SetCardInfo("Temple of Malady", 330, Rarity.RARE, mage.cards.t.TempleOfMalady.class)); + cards.add(new SetCardInfo("Temple of Malice", 331, Rarity.RARE, mage.cards.t.TempleOfMalice.class)); + cards.add(new SetCardInfo("Temple of Mystery", 332, Rarity.RARE, mage.cards.t.TempleOfMystery.class)); + cards.add(new SetCardInfo("Temple of Silence", 333, Rarity.RARE, mage.cards.t.TempleOfSilence.class)); + cards.add(new SetCardInfo("Temple of Triumph", 335, Rarity.RARE, mage.cards.t.TempleOfTriumph.class)); + cards.add(new SetCardInfo("Temple of the False God", 334, Rarity.UNCOMMON, mage.cards.t.TempleOfTheFalseGod.class)); + cards.add(new SetCardInfo("Tenured Inkcaster", 149, Rarity.UNCOMMON, mage.cards.t.TenuredInkcaster.class)); + cards.add(new SetCardInfo("Terramorphic Expanse", 336, Rarity.COMMON, mage.cards.t.TerramorphicExpanse.class)); + cards.add(new SetCardInfo("Tezzeret's Gambit", 117, Rarity.RARE, mage.cards.t.TezzeretsGambit.class)); + cards.add(new SetCardInfo("The Mending of Dominaria", 197, Rarity.RARE, mage.cards.t.TheMendingOfDominaria.class)); + cards.add(new SetCardInfo("The Mimeoplasm", 234, Rarity.RARE, mage.cards.t.TheMimeoplasm.class)); + cards.add(new SetCardInfo("Thief of Sanity", 243, Rarity.RARE, mage.cards.t.ThiefOfSanity.class)); + cards.add(new SetCardInfo("Thieving Amalgam", 150, Rarity.RARE, mage.cards.t.ThievingAmalgam.class)); + cards.add(new SetCardInfo("Thieving Skydiver", 118, Rarity.RARE, mage.cards.t.ThievingSkydiver.class)); + cards.add(new SetCardInfo("Think Twice", 119, Rarity.COMMON, mage.cards.t.ThinkTwice.class)); + cards.add(new SetCardInfo("Third Path Iconoclast", 244, Rarity.UNCOMMON, mage.cards.t.ThirdPathIconoclast.class)); + cards.add(new SetCardInfo("Three Visits", 209, Rarity.UNCOMMON, mage.cards.t.ThreeVisits.class)); + cards.add(new SetCardInfo("Thrilling Discovery", 245, Rarity.COMMON, mage.cards.t.ThrillingDiscovery.class)); + cards.add(new SetCardInfo("Titania, Protector of Argoth", 210, Rarity.MYTHIC, mage.cards.t.TitaniaProtectorOfArgoth.class)); + cards.add(new SetCardInfo("Trailblazer's Boots", 269, Rarity.UNCOMMON, mage.cards.t.TrailblazersBoots.class)); + cards.add(new SetCardInfo("Treasure Cruise", 120, Rarity.COMMON, mage.cards.t.TreasureCruise.class)); + cards.add(new SetCardInfo("Triton Shorestalker", 121, Rarity.COMMON, mage.cards.t.TritonShorestalker.class)); + cards.add(new SetCardInfo("Trygon Predator", 246, Rarity.UNCOMMON, mage.cards.t.TrygonPredator.class)); + cards.add(new SetCardInfo("Turntimber Sower", 211, Rarity.RARE, mage.cards.t.TurntimberSower.class)); + cards.add(new SetCardInfo("Twilight Mire", 337, Rarity.RARE, mage.cards.t.TwilightMire.class)); + cards.add(new SetCardInfo("Ukkima, Stalking Shadow", 247, Rarity.MYTHIC, mage.cards.u.UkkimaStalkingShadow.class)); + cards.add(new SetCardInfo("Underground River", 338, Rarity.RARE, mage.cards.u.UndergroundRiver.class)); + cards.add(new SetCardInfo("Unholy Heat", 182, Rarity.COMMON, mage.cards.u.UnholyHeat.class)); + cards.add(new SetCardInfo("Valorous Stance", 88, Rarity.UNCOMMON, mage.cards.v.ValorousStance.class)); + cards.add(new SetCardInfo("Vandalblast", 183, Rarity.UNCOMMON, mage.cards.v.Vandalblast.class)); + cards.add(new SetCardInfo("Vault of the Archangel", 339, Rarity.RARE, mage.cards.v.VaultOfTheArchangel.class)); + cards.add(new SetCardInfo("Veinwitch Coven", 151, Rarity.RARE, mage.cards.v.VeinwitchCoven.class)); + cards.add(new SetCardInfo("Vengeful Regrowth", 35, Rarity.RARE, mage.cards.v.VengefulRegrowth.class)); + cards.add(new SetCardInfo("Veyran, Voice of Duality", 248, Rarity.MYTHIC, mage.cards.v.VeyranVoiceOfDuality.class)); + cards.add(new SetCardInfo("Villainous Wealth", 249, Rarity.RARE, mage.cards.v.VillainousWealth.class)); + cards.add(new SetCardInfo("Viridescent Bog", 340, Rarity.RARE, mage.cards.v.ViridescentBog.class)); + cards.add(new SetCardInfo("Void Attendant", 212, Rarity.UNCOMMON, mage.cards.v.VoidAttendant.class)); + cards.add(new SetCardInfo("Volcanic Torrent", 184, Rarity.UNCOMMON, mage.cards.v.VolcanicTorrent.class)); + cards.add(new SetCardInfo("Whirler Rogue", 122, Rarity.UNCOMMON, mage.cards.w.WhirlerRogue.class)); + cards.add(new SetCardInfo("Windfall", 123, Rarity.UNCOMMON, mage.cards.w.Windfall.class)); + cards.add(new SetCardInfo("Winding Way", 213, Rarity.COMMON, mage.cards.w.WindingWay.class)); + cards.add(new SetCardInfo("Winged Boots", 124, Rarity.RARE, mage.cards.w.WingedBoots.class)); + cards.add(new SetCardInfo("Witch of the Moors", 152, Rarity.RARE, mage.cards.w.WitchOfTheMoors.class)); + cards.add(new SetCardInfo("Woodland Cemetery", 341, Rarity.RARE, mage.cards.w.WoodlandCemetery.class)); + cards.add(new SetCardInfo("World Shaper", 214, Rarity.RARE, mage.cards.w.WorldShaper.class)); + cards.add(new SetCardInfo("Wreck and Rebuild", 250, Rarity.UNCOMMON, mage.cards.w.WreckAndRebuild.class)); + cards.add(new SetCardInfo("Yavimaya Coast", 342, Rarity.RARE, mage.cards.y.YavimayaCoast.class)); + cards.add(new SetCardInfo("Young Pyromancer", 185, Rarity.UNCOMMON, mage.cards.y.YoungPyromancer.class)); + cards.add(new SetCardInfo("Yuma, Proud Protector", 4, Rarity.MYTHIC, mage.cards.y.YumaProudProtector.class)); + } +} diff --git a/Mage.Sets/src/mage/sets/RavnicaClueEdition.java b/Mage.Sets/src/mage/sets/RavnicaClueEdition.java index bbdc4825ff8..723c7e150f0 100644 --- a/Mage.Sets/src/mage/sets/RavnicaClueEdition.java +++ b/Mage.Sets/src/mage/sets/RavnicaClueEdition.java @@ -57,6 +57,7 @@ public final class RavnicaClueEdition extends ExpansionSet { cards.add(new SetCardInfo("Conservatory", 14, Rarity.UNCOMMON, mage.cards.c.Conservatory.class)); cards.add(new SetCardInfo("Consider", 84, Rarity.COMMON, mage.cards.c.Consider.class)); cards.add(new SetCardInfo("Coordinated Assault", 128, Rarity.UNCOMMON, mage.cards.c.CoordinatedAssault.class)); + cards.add(new SetCardInfo("Corporeal Projection", 28, Rarity.RARE, mage.cards.c.CorporealProjection.class)); cards.add(new SetCardInfo("Corpse Churn", 107, Rarity.COMMON, mage.cards.c.CorpseChurn.class)); cards.add(new SetCardInfo("Cosmotronic Wave", 129, Rarity.COMMON, mage.cards.c.CosmotronicWave.class)); cards.add(new SetCardInfo("Council's Judgment", 57, Rarity.RARE, mage.cards.c.CouncilsJudgment.class)); @@ -115,6 +116,7 @@ public final class RavnicaClueEdition extends ExpansionSet { cards.add(new SetCardInfo("Hall", 16, Rarity.UNCOMMON, mage.cards.h.Hall.class)); cards.add(new SetCardInfo("Hallowed Fountain", 277, Rarity.RARE, mage.cards.h.HallowedFountain.class)); 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("Hydroid Krasis", 195, Rarity.MYTHIC, mage.cards.h.HydroidKrasis.class)); cards.add(new SetCardInfo("Hypersonic Dragon", 196, Rarity.RARE, mage.cards.h.HypersonicDragon.class)); @@ -254,6 +256,7 @@ public final class RavnicaClueEdition extends ExpansionSet { cards.add(new SetCardInfo("Twilight Panther", 78, Rarity.COMMON, mage.cards.t.TwilightPanther.class)); cards.add(new SetCardInfo("Undercover Butler", 49, Rarity.UNCOMMON, mage.cards.u.UndercoverButler.class)); cards.add(new SetCardInfo("Underrealm Lich", 215, Rarity.MYTHIC, mage.cards.u.UnderrealmLich.class)); + cards.add(new SetCardInfo("Unruly Krasis", 50, Rarity.RARE, mage.cards.u.UnrulyKrasis.class)); cards.add(new SetCardInfo("Urbis Protector", 79, Rarity.UNCOMMON, mage.cards.u.UrbisProtector.class)); cards.add(new SetCardInfo("Utvara Scalper", 153, Rarity.COMMON, mage.cards.u.UtvaraScalper.class)); cards.add(new SetCardInfo("Venomous Hierophant", 125, Rarity.COMMON, mage.cards.v.VenomousHierophant.class)); diff --git a/Mage.Sets/src/mage/sets/SpecialGuests.java b/Mage.Sets/src/mage/sets/SpecialGuests.java index ec92bd078b2..06c36b2e08e 100644 --- a/Mage.Sets/src/mage/sets/SpecialGuests.java +++ b/Mage.Sets/src/mage/sets/SpecialGuests.java @@ -21,11 +21,14 @@ public final class SpecialGuests extends ExpansionSet { this.hasBoosters = false; this.hasBasicLands = false; + cards.add(new SetCardInfo("Brazen Borrower", 30, Rarity.MYTHIC, mage.cards.b.BrazenBorrower.class)); cards.add(new SetCardInfo("Breeches, Brazen Plunderer", 6, Rarity.UNCOMMON, mage.cards.b.BreechesBrazenPlunderer.class)); cards.add(new SetCardInfo("Bridge from Below", 3, Rarity.RARE, mage.cards.b.BridgeFromBelow.class)); cards.add(new SetCardInfo("Carnage Tyrant", 10, Rarity.MYTHIC, mage.cards.c.CarnageTyrant.class)); cards.add(new SetCardInfo("Crashing Footfalls", 25, Rarity.MYTHIC, mage.cards.c.CrashingFootfalls.class)); cards.add(new SetCardInfo("Dargo, the Shipwrecker", 7, Rarity.UNCOMMON, mage.cards.d.DargoTheShipwrecker.class)); + cards.add(new SetCardInfo("Desert", 37, Rarity.MYTHIC, mage.cards.d.Desert.class)); + cards.add(new SetCardInfo("Desertion", 31, Rarity.MYTHIC, mage.cards.d.Desertion.class)); cards.add(new SetCardInfo("Drown in the Loch", 27, Rarity.MYTHIC, mage.cards.d.DrownInTheLoch.class)); cards.add(new SetCardInfo("Fabricate", 20, Rarity.MYTHIC, mage.cards.f.Fabricate.class)); cards.add(new SetCardInfo("Field of the Dead", 28, Rarity.MYTHIC, mage.cards.f.FieldOfTheDead.class)); @@ -45,11 +48,20 @@ public final class SpecialGuests extends ExpansionSet { cards.add(new SetCardInfo("Mana Crypt", 17, Rarity.MYTHIC, mage.cards.m.ManaCrypt.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mephidross Vampire", 4, Rarity.RARE, mage.cards.m.MephidrossVampire.class)); cards.add(new SetCardInfo("Mirri, Weatherlight Duelist", 15, Rarity.MYTHIC, mage.cards.m.MirriWeatherlightDuelist.class)); + cards.add(new SetCardInfo("Morbid Opportunist", 32, Rarity.MYTHIC, mage.cards.m.MorbidOpportunist.class)); + cards.add(new SetCardInfo("Mystic Snake", 35, Rarity.MYTHIC, mage.cards.m.MysticSnake.class)); + cards.add(new SetCardInfo("Notion Thief", 36, Rarity.MYTHIC, mage.cards.n.NotionThief.class)); cards.add(new SetCardInfo("Pitiless Plunderer", 5, Rarity.UNCOMMON, mage.cards.p.PitilessPlunderer.class)); cards.add(new SetCardInfo("Polyraptor", 12, Rarity.MYTHIC, mage.cards.p.Polyraptor.class)); + cards.add(new SetCardInfo("Port Razer", 33, Rarity.MYTHIC, mage.cards.p.PortRazer.class)); + cards.add(new SetCardInfo("Prismatic Vista", 38, Rarity.MYTHIC, mage.cards.p.PrismaticVista.class)); cards.add(new SetCardInfo("Rampaging Ferocidon", 8, Rarity.RARE, mage.cards.r.RampagingFerocidon.class)); + cards.add(new SetCardInfo("Scapeshift", 34, Rarity.MYTHIC, mage.cards.s.Scapeshift.class)); cards.add(new SetCardInfo("Show and Tell", 21, Rarity.MYTHIC, mage.cards.s.ShowAndTell.class)); + cards.add(new SetCardInfo("Solitude", 44, Rarity.MYTHIC, mage.cards.s.Solitude.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Solitude", 49, Rarity.MYTHIC, mage.cards.s.Solitude.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Star Compass", 18, Rarity.UNCOMMON, mage.cards.s.StarCompass.class)); + cards.add(new SetCardInfo("Stoneforge Mystic", 29, Rarity.MYTHIC, mage.cards.s.StoneforgeMystic.class)); cards.add(new SetCardInfo("Thrasios, Triton Hero", 16, Rarity.RARE, mage.cards.t.ThrasiosTritonHero.class)); cards.add(new SetCardInfo("Tireless Tracker", 26, Rarity.MYTHIC, mage.cards.t.TirelessTracker.class)); cards.add(new SetCardInfo("Tragic Slip", 22, Rarity.MYTHIC, mage.cards.t.TragicSlip.class)); diff --git a/Mage.Sets/src/mage/sets/TheBigScore.java b/Mage.Sets/src/mage/sets/TheBigScore.java index 83d9b87a3a9..a5659134f9b 100644 --- a/Mage.Sets/src/mage/sets/TheBigScore.java +++ b/Mage.Sets/src/mage/sets/TheBigScore.java @@ -1,6 +1,7 @@ package mage.sets; import mage.cards.ExpansionSet; +import mage.constants.Rarity; import mage.constants.SetType; /** @@ -19,5 +20,101 @@ public final class TheBigScore extends ExpansionSet { this.blockName = "Outlaws of Thunder Junction"; this.hasBasicLands = false; this.hasBoosters = false; + + cards.add(new SetCardInfo("Ancient Cornucopia", 16, Rarity.MYTHIC, mage.cards.a.AncientCornucopia.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ancient Cornucopia", 46, Rarity.MYTHIC, mage.cards.a.AncientCornucopia.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ancient Cornucopia", 81, Rarity.MYTHIC, mage.cards.a.AncientCornucopia.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bristlebud Farmer", 17, Rarity.MYTHIC, mage.cards.b.BristlebudFarmer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bristlebud Farmer", 47, Rarity.MYTHIC, mage.cards.b.BristlebudFarmer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Bristlebud Farmer", 82, Rarity.MYTHIC, mage.cards.b.BristlebudFarmer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Collector's Cage", 1, Rarity.MYTHIC, mage.cards.c.CollectorsCage.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Collector's Cage", 31, Rarity.MYTHIC, mage.cards.c.CollectorsCage.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Collector's Cage", 66, Rarity.MYTHIC, mage.cards.c.CollectorsCage.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Esoteric Duplicator", 35, Rarity.MYTHIC, mage.cards.e.EsotericDuplicator.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Esoteric Duplicator", 5, Rarity.MYTHIC, mage.cards.e.EsotericDuplicator.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Esoteric Duplicator", 70, Rarity.MYTHIC, mage.cards.e.EsotericDuplicator.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Fomori Vault", 29, Rarity.MYTHIC, mage.cards.f.FomoriVault.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Fomori Vault", 59, Rarity.MYTHIC, mage.cards.f.FomoriVault.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Fomori Vault", 94, Rarity.MYTHIC, mage.cards.f.FomoriVault.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Generous Plunderer", 11, Rarity.MYTHIC, mage.cards.g.GenerousPlunderer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Generous Plunderer", 41, Rarity.MYTHIC, mage.cards.g.GenerousPlunderer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Generous Plunderer", 76, Rarity.MYTHIC, mage.cards.g.GenerousPlunderer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Grand Abolisher", 2, Rarity.MYTHIC, mage.cards.g.GrandAbolisher.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Grand Abolisher", 32, Rarity.MYTHIC, mage.cards.g.GrandAbolisher.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Grand Abolisher", 67, Rarity.MYTHIC, mage.cards.g.GrandAbolisher.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Greed's Gambit", 38, Rarity.MYTHIC, mage.cards.g.GreedsGambit.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Greed's Gambit", 73, Rarity.MYTHIC, mage.cards.g.GreedsGambit.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Greed's Gambit", 8, Rarity.MYTHIC, mage.cards.g.GreedsGambit.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Harvester of Misery", 39, Rarity.MYTHIC, mage.cards.h.HarvesterOfMisery.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Harvester of Misery", 74, Rarity.MYTHIC, mage.cards.h.HarvesterOfMisery.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Harvester of Misery", 9, Rarity.MYTHIC, mage.cards.h.HarvesterOfMisery.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hostile Investigator", 10, Rarity.MYTHIC, mage.cards.h.HostileInvestigator.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hostile Investigator", 40, Rarity.MYTHIC, mage.cards.h.HostileInvestigator.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Hostile Investigator", 75, Rarity.MYTHIC, mage.cards.h.HostileInvestigator.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Legion Extruder", 12, Rarity.MYTHIC, mage.cards.l.LegionExtruder.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Legion Extruder", 42, Rarity.MYTHIC, mage.cards.l.LegionExtruder.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Legion Extruder", 77, Rarity.MYTHIC, mage.cards.l.LegionExtruder.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Loot, the Key to Everything", 21, Rarity.MYTHIC, mage.cards.l.LootTheKeyToEverything.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Loot, the Key to Everything", 51, Rarity.MYTHIC, mage.cards.l.LootTheKeyToEverything.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Loot, the Key to Everything", 62, Rarity.MYTHIC, mage.cards.l.LootTheKeyToEverything.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Loot, the Key to Everything", 86, Rarity.MYTHIC, mage.cards.l.LootTheKeyToEverything.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lost Jitte", 23, Rarity.MYTHIC, mage.cards.l.LostJitte.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lost Jitte", 53, Rarity.MYTHIC, mage.cards.l.LostJitte.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lost Jitte", 88, Rarity.MYTHIC, mage.cards.l.LostJitte.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lotus Ring", 24, Rarity.MYTHIC, mage.cards.l.LotusRing.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lotus Ring", 54, Rarity.MYTHIC, mage.cards.l.LotusRing.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lotus Ring", 63, Rarity.MYTHIC, mage.cards.l.LotusRing.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lotus Ring", 89, Rarity.MYTHIC, mage.cards.l.LotusRing.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Memory Vessel", 13, Rarity.MYTHIC, mage.cards.m.MemoryVessel.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Memory Vessel", 43, Rarity.MYTHIC, mage.cards.m.MemoryVessel.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Memory Vessel", 78, Rarity.MYTHIC, mage.cards.m.MemoryVessel.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Molten Duplication", 14, Rarity.MYTHIC, mage.cards.m.MoltenDuplication.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Molten Duplication", 44, Rarity.MYTHIC, mage.cards.m.MoltenDuplication.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Molten Duplication", 79, Rarity.MYTHIC, mage.cards.m.MoltenDuplication.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Nexus of Becoming", 25, Rarity.MYTHIC, mage.cards.n.NexusOfBecoming.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Nexus of Becoming", 55, Rarity.MYTHIC, mage.cards.n.NexusOfBecoming.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Nexus of Becoming", 90, Rarity.MYTHIC, mage.cards.n.NexusOfBecoming.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Oltec Matterweaver", 3, Rarity.MYTHIC, mage.cards.o.OltecMatterweaver.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Oltec Matterweaver", 33, Rarity.MYTHIC, mage.cards.o.OltecMatterweaver.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Oltec Matterweaver", 68, Rarity.MYTHIC, mage.cards.o.OltecMatterweaver.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Omenpath Journey", 18, Rarity.MYTHIC, mage.cards.o.OmenpathJourney.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Omenpath Journey", 48, Rarity.MYTHIC, mage.cards.o.OmenpathJourney.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Omenpath Journey", 83, Rarity.MYTHIC, mage.cards.o.OmenpathJourney.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Pest Control", 22, Rarity.MYTHIC, mage.cards.p.PestControl.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Pest Control", 52, Rarity.MYTHIC, mage.cards.p.PestControl.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Pest Control", 87, Rarity.MYTHIC, mage.cards.p.PestControl.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Rest in Peace", 34, Rarity.MYTHIC, mage.cards.r.RestInPeace.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Rest in Peace", 4, Rarity.MYTHIC, mage.cards.r.RestInPeace.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Rest in Peace", 69, Rarity.MYTHIC, mage.cards.r.RestInPeace.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sandstorm Salvager", 19, Rarity.MYTHIC, mage.cards.s.SandstormSalvager.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sandstorm Salvager", 49, Rarity.MYTHIC, mage.cards.s.SandstormSalvager.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sandstorm Salvager", 84, Rarity.MYTHIC, mage.cards.s.SandstormSalvager.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Simulacrum Synthesizer", 36, Rarity.MYTHIC, mage.cards.s.SimulacrumSynthesizer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Simulacrum Synthesizer", 6, Rarity.MYTHIC, mage.cards.s.SimulacrumSynthesizer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Simulacrum Synthesizer", 71, Rarity.MYTHIC, mage.cards.s.SimulacrumSynthesizer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sword of Wealth and Power", 26, Rarity.MYTHIC, mage.cards.s.SwordOfWealthAndPower.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sword of Wealth and Power", 56, Rarity.MYTHIC, mage.cards.s.SwordOfWealthAndPower.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sword of Wealth and Power", 64, Rarity.MYTHIC, mage.cards.s.SwordOfWealthAndPower.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sword of Wealth and Power", 91, Rarity.MYTHIC, mage.cards.s.SwordOfWealthAndPower.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tarnation Vista", 30, Rarity.MYTHIC, mage.cards.t.TarnationVista.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tarnation Vista", 60, Rarity.MYTHIC, mage.cards.t.TarnationVista.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tarnation Vista", 65, Rarity.MYTHIC, mage.cards.t.TarnationVista.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tarnation Vista", 95, Rarity.MYTHIC, mage.cards.t.TarnationVista.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Territory Forge", 15, Rarity.MYTHIC, mage.cards.t.TerritoryForge.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Territory Forge", 45, Rarity.MYTHIC, mage.cards.t.TerritoryForge.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Territory Forge", 80, Rarity.MYTHIC, mage.cards.t.TerritoryForge.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Torpor Orb", 27, Rarity.MYTHIC, mage.cards.t.TorporOrb.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Torpor Orb", 57, Rarity.MYTHIC, mage.cards.t.TorporOrb.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Torpor Orb", 92, Rarity.MYTHIC, mage.cards.t.TorporOrb.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Transmutation Font", 28, Rarity.MYTHIC, mage.cards.t.TransmutationFont.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Transmutation Font", 58, Rarity.MYTHIC, mage.cards.t.TransmutationFont.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Transmutation Font", 93, Rarity.MYTHIC, mage.cards.t.TransmutationFont.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vaultborn Tyrant", 20, Rarity.MYTHIC, mage.cards.v.VaultbornTyrant.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vaultborn Tyrant", 50, Rarity.MYTHIC, mage.cards.v.VaultbornTyrant.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vaultborn Tyrant", 61, Rarity.MYTHIC, mage.cards.v.VaultbornTyrant.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vaultborn Tyrant", 85, Rarity.MYTHIC, mage.cards.v.VaultbornTyrant.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Worldwalker Helm", 37, Rarity.MYTHIC, mage.cards.w.WorldwalkerHelm.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Worldwalker Helm", 7, Rarity.MYTHIC, mage.cards.w.WorldwalkerHelm.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Worldwalker Helm", 72, Rarity.MYTHIC, mage.cards.w.WorldwalkerHelm.class, NON_FULL_USE_VARIOUS)); } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/CommittedCrimeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/CommittedCrimeTest.java new file mode 100644 index 00000000000..4ac9a90ad88 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/CommittedCrimeTest.java @@ -0,0 +1,275 @@ +package org.mage.test.cards.abilities.keywords; + +import mage.abilities.common.CommittedCrimeTriggeredAbility; +import mage.abilities.effects.common.GainLifeEffect; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author TheElk801 + */ +public class CommittedCrimeTest extends CardTestPlayerBase { + + private void makeTester() { + addCustomCardWithAbility( + "tester", playerA, new CommittedCrimeTriggeredAbility(new GainLifeEffect(1), false) + ); + } + + private static final String spike = "Lava Spike"; + + @Test + public void testSpikeOpponent() { + makeTester(); + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + addCard(Zone.HAND, playerA, spike); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, spike, playerB); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerA, 20 + 1); + assertLife(playerB, 20 - 3); + } + + @Test + public void testSpikeSelf() { + makeTester(); + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + addCard(Zone.HAND, playerA, spike); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, spike, playerA); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerA, 20 - 3); + assertLife(playerB, 20); + } + + private static final String voidslime = "Voidslime"; + private static final String sanctifier = "Cathedral Sanctifier"; + + @Test + public void testCounterSpellOpponent() { + makeTester(); + addCard(Zone.BATTLEFIELD, playerA, "Tropical Island", 3); + addCard(Zone.BATTLEFIELD, playerB, "Plains"); + addCard(Zone.HAND, playerA, voidslime); + addCard(Zone.HAND, playerB, sanctifier); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, sanctifier); + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, voidslime, sanctifier); + + setStrictChooseMode(true); + setStopAt(2, PhaseStep.END_TURN); + execute(); + + assertLife(playerA, 20 + 1); + } + + @Test + public void testCounterAbilityOpponent() { + makeTester(); + addCard(Zone.BATTLEFIELD, playerA, "Tropical Island", 3); + addCard(Zone.BATTLEFIELD, playerB, "Plains"); + addCard(Zone.HAND, playerA, voidslime); + addCard(Zone.HAND, playerB, sanctifier); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, sanctifier); + waitStackResolved(2, PhaseStep.PRECOMBAT_MAIN, true); + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, voidslime, "stack"); + + setStrictChooseMode(true); + setStopAt(2, PhaseStep.END_TURN); + execute(); + + assertLife(playerA, 20 + 1); + assertLife(playerB, 20); + } + + @Test + public void testCounterSpellSelf() { + makeTester(); + addCard(Zone.BATTLEFIELD, playerA, "Tropical Island", 3); + addCard(Zone.BATTLEFIELD, playerA, "Plains"); + addCard(Zone.HAND, playerA, voidslime); + addCard(Zone.HAND, playerA, sanctifier); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, sanctifier); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, voidslime, sanctifier); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerA, 20); + } + + @Test + public void testCounterAbilitySelf() { + makeTester(); + addCard(Zone.BATTLEFIELD, playerA, "Tropical Island", 3); + addCard(Zone.BATTLEFIELD, playerA, "Plains"); + addCard(Zone.HAND, playerA, voidslime); + addCard(Zone.HAND, playerA, sanctifier); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, sanctifier); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, voidslime, "stack"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerA, 20); + } + + private static final String bear = "Grizzly Bears"; + private static final String murder = "Murder"; + + @Test + public void testMurderOpponent() { + makeTester(); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); + addCard(Zone.BATTLEFIELD, playerB, bear); + addCard(Zone.HAND, playerA, murder); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, murder, bear); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerB, bear, 0); + assertGraveyardCount(playerB, bear, 1); + assertGraveyardCount(playerA, murder, 1); + assertLife(playerA, 20 + 1); + } + + @Test + public void testMurderSelf() { + makeTester(); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); + addCard(Zone.BATTLEFIELD, playerA, bear); + addCard(Zone.HAND, playerA, murder); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, murder, bear); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, bear, 0); + assertGraveyardCount(playerA, bear, 1); + assertGraveyardCount(playerA, murder, 1); + assertLife(playerA, 20); + } + + private static final String purge = "Coffin Purge"; + + @Test + public void testGraveyardOpponent() { + makeTester(); + addCard(Zone.BATTLEFIELD, playerA, "Swamp"); + addCard(Zone.HAND, playerA, purge); + addCard(Zone.GRAVEYARD, playerB, bear); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, purge, bear); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerB, bear, 0); + assertExileCount(playerB, bear, 1); + assertLife(playerA, 20 + 1); + } + + @Test + public void testGraveyardSelf() { + makeTester(); + addCard(Zone.BATTLEFIELD, playerA, "Swamp"); + addCard(Zone.HAND, playerA, purge); + addCard(Zone.GRAVEYARD, playerA, bear); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, purge, bear); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerA, bear, 0); + assertExileCount(playerA, bear, 1); + assertLife(playerA, 20); + } + + private static final String desert = "Sunscorched Desert"; + + @Test + public void testTriggerOpponent() { + makeTester(); + addCard(Zone.HAND, playerA, desert); + + playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, desert); + addTarget(playerA, playerB); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerA, 20 + 1); + assertLife(playerB, 20 - 1); + } + + @Test + public void testTriggerSelf() { + makeTester(); + addCard(Zone.HAND, playerA, desert); + + playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, desert); + addTarget(playerA, playerA); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerA, 20 - 1); + } + + private static final String fireslinger = "Goblin Fireslinger"; + + @Test + public void testActivateOpponent() { + makeTester(); + addCard(Zone.BATTLEFIELD, playerA, fireslinger); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}", playerB); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerA, 20 + 1); + assertLife(playerB, 20 - 1); + } + + @Test + public void testActivateSelf() { + makeTester(); + addCard(Zone.BATTLEFIELD, playerA, fireslinger); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}", playerA); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerA, 20 - 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/DisguiseTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/DisguiseTest.java index 5befc0cfd8e..5a168aaca66 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/DisguiseTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/DisguiseTest.java @@ -173,4 +173,51 @@ public class DisguiseTest extends CardTestPlayerBase { Assert.assertFalse(info, foundAbility != null); } } + + @Test + public void testCostReduction() { + String chisel = "Dream Chisel"; // Face-down creature spells you cast cost {1} less to cast. + String nightdrinker = "Nightdrinker Moroii"; + /* Nightdrinker Moroii {3}{B} Creature — Vampire + * Flying + * When Nightdrinker Moroii enters the battlefield, you lose 3 life. + * Disguise {B}{B} + */ + addCard(Zone.BATTLEFIELD, playerA, chisel); + addCard(Zone.HAND, playerA, nightdrinker); + addCard(Zone.BATTLEFIELD, playerA, "Wastes", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, nightdrinker + " using Disguise"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); + assertLife(playerA, 20); + } + + @Test + public void testCostAdjuster() { + /* Fugitive Codebreaker {1}{R} + * Creature — Goblin Rogue + * Prowess, haste + * Disguise {5}{R}. This cost is reduced by {1} for each instant and sorcery card in your graveyard. + * When Fugitive Codebreaker is turned face up, discard your hand, then draw three cards. + */ + String codebreaker = "Fugitive Codebreaker"; + addCard(Zone.HAND, playerA, codebreaker); + addCard(Zone.GRAVEYARD, playerA, "Lightning Bolt", 3); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3 + 3); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, codebreaker + " using Disguise", true); + activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{5}{R}:"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertHandCount(playerA, 3); + } + } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MorphTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MorphTest.java index 2c2837c3043..5f8b5fd06a9 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MorphTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MorphTest.java @@ -1294,4 +1294,23 @@ public class MorphTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.END_TURN); execute(); } + + @Test + public void test_Morph_HoodedHydra() { + // Morph {2} + addCard(Zone.HAND, playerA, "Hooded Hydra"); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3+5); + + // prepare face down + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Hooded Hydra using Morph"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{3}{G}{G}: Turn this face-down permanent face up."); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertPermanentCount(playerA, "Hooded Hydra", 1); + assertPowerToughness(playerA, "Hooded Hydra", 5, 5); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/PlotTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/PlotTest.java new file mode 100644 index 00000000000..242d818499b --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/PlotTest.java @@ -0,0 +1,284 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.mage.test.cards.abilities.keywords; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class PlotTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.d.DjinnOfFoolsFall Djinn of Fool's Fall} {4}{U} + * Creature — Djinn + * Flying + * Plot {3}{U} + * 4/3 + */ + private static final String djinn = "Djinn of Fool's Fall"; + + @Test + public void TestSimplePlot() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Island", 4); + addCard(Zone.HAND, playerA, djinn); + + checkPlayableAbility("plot can't be used during upkeep", 1, PhaseStep.UPKEEP, playerA, "Plot", false); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Plot"); + checkExileCount("plot is in exile", 1, PhaseStep.PRECOMBAT_MAIN, playerA, djinn, 1); + + checkPlayableAbility("Can not be cast on same turn", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast " + djinn + " using Plot", false); + checkPlayableAbility("Can not be cast on opponent turn", 2, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + djinn + " using Plot", false); + checkPlayableAbility("Can not be cast on non-main phase", 3, PhaseStep.UPKEEP, playerA, "Cast " + djinn + " using Plot", false); + checkPlayableAbility("Can be cast on main phase", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + djinn + " using Plot", true); + + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, djinn + " using Plot"); + + setStopAt(3, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, djinn, 1); + assertTappedCount("Island", true, 0); + } + + /** + * {@link mage.cards.l.LonghornSharpshooter Longhorn Sharpshooter} {2}{R} + * Creature — Minotaur Rogue + * Reach + * When Longhorn Sharpshooter becomes plotted, it deals 2 damage to any target. + * Plot {3}{R} (You may pay {3}{R} and exile this card from your hand. Cast it as a sorcery on a later turn without paying its mana cost. Plot only as a sorcery.) + * 3/3 + */ + private static final String sharpshooter = "Longhorn Sharpshooter"; + + @Test + public void TestSharpshooterTrigger() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); + addCard(Zone.HAND, playerA, sharpshooter); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Plot"); + addTarget(playerA, playerB); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertExileCount(playerA, sharpshooter, 1); + assertLife(playerB, 20 - 2); + } + + /** + * {@link mage.cards.k.KellanJoinsUp Kellan Joins Up} {G}{W}{U} + * Legendary Enchantment + *

+ * When Kellan Joins Up enters the battlefield, you may exile a nonland card with mana value 3 or less from your hand. If you do, it becomes plotted. (You may cast it as a sorcery on a later turn without paying its mana cost.) + *

+ * Whenever a legendary creature enters the battlefield under your control, put a +1/+1 counter on each creature you control. + */ + private static final String kellanJoinsUp = "Kellan Joins Up"; + + @Test + public void TestKellanJoinsUpTriggerSharpshooter() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 1); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + addCard(Zone.HAND, playerA, kellanJoinsUp); + addCard(Zone.HAND, playerA, sharpshooter); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, kellanJoinsUp); + addTarget(playerA, sharpshooter); // choose sharpshooter to exile & plot + addTarget(playerA, playerB); // sharpshooter does trigger and deals 2 + + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, sharpshooter + " using Plot"); + + setStopAt(3, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, sharpshooter, 1); + assertLife(playerB, 20 - 2); + } + + @Test + public void TestPlottingInstant() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 1); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + addCard(Zone.HAND, playerA, kellanJoinsUp); + addCard(Zone.HAND, playerA, "Lightning Bolt"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, kellanJoinsUp); + addTarget(playerA, "Lightning Bolt"); // choose Bolt to exile & plot + + checkPlayableAbility("Can not be cast on non-main phase", 3, PhaseStep.UPKEEP, playerA, "Cast Lightning Bolt using Plot", false); + checkPlayableAbility("Can be cast on main phase", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Lightning Bolt using Plot", true); + + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt using Plot", playerB); + + setStopAt(3, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Lightning Bolt", 1); + assertLife(playerB, 20 - 3); + } + + @Test + public void TestPlottingAdventure_CastRegularSide() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 1); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + addCard(Zone.HAND, playerA, kellanJoinsUp); + addCard(Zone.HAND, playerA, "Bramble Familiar"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, kellanJoinsUp); + addTarget(playerA, "Bramble Familiar"); + + checkPlayableAbility("Can not be cast on non-main phase", 3, PhaseStep.UPKEEP, playerA, "Cast Bramble Familiar using Plot", false); + checkPlayableAbility("Can not be cast on non-main phase", 3, PhaseStep.UPKEEP, playerA, "Cast Fetch Quest using Plot", false); + checkPlayableAbility("Can be cast on main phase", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Bramble Familiar using Plot", true); + checkPlayableAbility("Can be cast on main phase", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Fetch Quest using Plot", true); + + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Bramble Familiar using Plot"); + + setStopAt(3, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Bramble Familiar", 1); + } + + @Test + public void TestPlottingAdventure_CastAdventureSide() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 1); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + addCard(Zone.HAND, playerA, kellanJoinsUp); + addCard(Zone.HAND, playerA, "Bramble Familiar"); + addCard(Zone.LIBRARY, playerA, "Plateau", 2); // The card. Add 2 since one will be drawn t3 + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, kellanJoinsUp); + addTarget(playerA, "Bramble Familiar"); + + checkPlayableAbility("Can not be cast on non-main phase", 3, PhaseStep.UPKEEP, playerA, "Cast Bramble Familiar using Plot", false); + checkPlayableAbility("Can not be cast on non-main phase", 3, PhaseStep.UPKEEP, playerA, "Cast Fetch Quest using Plot", false); + checkPlayableAbility("Can be cast on main phase", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Bramble Familiar using Plot", true); + checkPlayableAbility("Can be cast on main phase", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Fetch Quest using Plot", true); + + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Fetch Quest using Plot"); + setChoice(playerA, "Plateau"); // choice for Fetch Quest + + setStopAt(3, PhaseStep.BEGIN_COMBAT); + execute(); + + assertExileCount(playerA, "Bramble Familiar", 1); // The card is back in exile, but as an adventuring card, not a plotted one. + assertPermanentCount(playerA, "Plateau", 1); + } + + @Test + public void TestPlottingSplit_CastLeft() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 1); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + addCard(Zone.HAND, playerA, kellanJoinsUp); + addCard(Zone.HAND, playerA, "Wear // Tear"); + addCard(Zone.BATTLEFIELD, playerB, "Memnite"); + addCard(Zone.BATTLEFIELD, playerB, "Glorious Anthem"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, kellanJoinsUp); + addTarget(playerA, "Wear // Tear"); + + checkExileCount("assert the full card is in exile", 1, PhaseStep.BEGIN_COMBAT, playerA, "Wear // Tear", 1); + + checkPlayableAbility("Can not be cast on non-main phase", 3, PhaseStep.UPKEEP, playerA, "Cast Wear using Plot", false); + checkPlayableAbility("Can not be cast on non-main phase", 3, PhaseStep.UPKEEP, playerA, "Cast Tear using Plot", false); + checkPlayableAbility("Can be cast on main phase", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Wear using Plot", true); + checkPlayableAbility("Can be cast on main phase", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Tear using Plot", true); + + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Wear using Plot", "Memnite"); + + setStopAt(3, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerB, "Memnite", 0); + assertPermanentCount(playerB, "Glorious Anthem", 1); + assertGraveyardCount(playerA, "Wear // Tear", 1); + } + + @Test + public void TestPlottingSplit_CastRight() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 1); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + addCard(Zone.HAND, playerA, kellanJoinsUp); + addCard(Zone.HAND, playerA, "Wear // Tear"); + addCard(Zone.BATTLEFIELD, playerB, "Memnite"); + addCard(Zone.BATTLEFIELD, playerB, "Glorious Anthem"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, kellanJoinsUp); + addTarget(playerA, "Wear // Tear"); + + checkExileCount("assert the full card is in exile", 1, PhaseStep.BEGIN_COMBAT, playerA, "Wear // Tear", 1); + + checkPlayableAbility("Can not be cast on non-main phase", 3, PhaseStep.UPKEEP, playerA, "Cast Wear using Plot", false); + checkPlayableAbility("Can not be cast on non-main phase", 3, PhaseStep.UPKEEP, playerA, "Cast Tear using Plot", false); + checkPlayableAbility("Can be cast on main phase", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Wear using Plot", true); + checkPlayableAbility("Can be cast on main phase", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Tear using Plot", true); + + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Tear using Plot", "Glorious Anthem"); + + setStopAt(3, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerB, "Memnite", 1); + assertPermanentCount(playerB, "Glorious Anthem", 0); + assertGraveyardCount(playerA, "Wear // Tear", 1); + } + + @Test + public void TestPlottingMDFC() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 1); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + addCard(Zone.HAND, playerA, kellanJoinsUp); + addCard(Zone.HAND, playerA, "Tangled Florahedron // Tangled Vale"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, kellanJoinsUp); + addTarget(playerA, "Tangled Florahedron"); + + checkExileCount("assert the card is in exile", 1, PhaseStep.BEGIN_COMBAT, playerA, "Tangled Florahedron", 1); + + checkPlayableAbility("Can not be cast on non-main phase", 3, PhaseStep.UPKEEP, playerA, "Cast Tangled Florahedron using Plot", false); + checkPlayableAbility("Can not cast lands", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Tangled Vale using Plot", false); + checkPlayableAbility("Can be cast on main phase", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Tangled Florahedron using Plot", true); + + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Tangled Florahedron using Plot"); + + setStopAt(3, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Tangled Florahedron", 1); + } +} 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 new file mode 100644 index 00000000000..855082b3938 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SaddleTest.java @@ -0,0 +1,133 @@ +package org.mage.test.cards.abilities.keywords; + +import mage.abilities.keyword.MenaceAbility; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.game.permanent.Permanent; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author TheElk801 + */ +public class SaddleTest extends CardTestPlayerBase { + + private static final String charger = "Quilled Charger"; + private static final String bear = "Grizzly Bears"; + + private void assertSaddled(String name, boolean saddled) { + Permanent permanent = getPermanent(name); + Assert.assertEquals( + name + " should " + (saddled ? "" : "not ") + "be saddled", + saddled, permanent.isSaddled() + ); + } + + @Test + public void testNoSaddle() { + addCard(Zone.BATTLEFIELD, playerA, charger); + + attack(1, playerA, charger, playerB); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertTapped(charger, true); + assertSaddled(charger, false); + assertAbility(playerA, charger, new MenaceAbility(false), false); + assertLife(playerB, 20 - 4); + } + + @Test + public void testSaddle() { + addCard(Zone.BATTLEFIELD, playerA, charger); + addCard(Zone.BATTLEFIELD, playerA, bear); + + setChoice(playerA, bear); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Saddle"); + + attack(1, playerA, charger, playerB); + + 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(); + + assertSaddled(charger, false); + } + + private static final String possum = "Rambling Possum"; + private static final String lion = "Silvercoat Lion"; + + @Test + public void testSaddledThisTurn() { + addCard(Zone.BATTLEFIELD, playerA, possum); + addCard(Zone.BATTLEFIELD, playerA, bear); + addCard(Zone.BATTLEFIELD, playerA, lion); + + setChoice(playerA, bear); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Saddle"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + + attack(1, playerA, possum, playerB); + setChoice(playerA, bear); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, bear, 0); + assertHandCount(playerA, bear, 1); + assertTapped(lion, false); + assertTapped(possum, true); + assertSaddled(possum, true); + assertLife(playerB, 20 - 3 - 1); + } + + @Test + public void testSaddledThisTurnFail() { + addCard(Zone.BATTLEFIELD, playerA, possum); + addCard(Zone.BATTLEFIELD, playerA, bear); + addCard(Zone.BATTLEFIELD, playerA, lion); + + setChoice(playerA, bear); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Saddle"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + + attack(1, playerA, possum, playerB); + setChoice(playerA, lion); + + setStopAt(1, PhaseStep.END_TURN); + try { + execute(); + } catch (AssertionError e) { + Assert.assertEquals( + "Lion can't be targeted", + "Missing CHOICE def for turn 1, step DECLARE_ATTACKERS, PlayerA\n" + + "Object: PermanentCard: Rambling Possum;\n" + + "Target: TargetPermanent: Select creatures that saddled it this turn (selected 0)", + e.getMessage() + ); + } + + assertTapped(bear, true); + assertTapped(lion, false); + assertTapped(possum, true); + assertSaddled(possum, true); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SpreeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SpreeTest.java new file mode 100644 index 00000000000..d15f224a4a2 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SpreeTest.java @@ -0,0 +1,95 @@ +package org.mage.test.cards.abilities.keywords; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author TheElk801 + */ +public class SpreeTest extends CardTestPlayerBase { + + private static final String accident = "Unfortunate Accident"; + // Instant {B} + // Spree + // + {2}{B} -- Destroy target creature. + // + {1} -- Create a 1/1 Mercenary token + + private static final String bear = "Grizzly Bears"; + + @Test + public void testFirstMode() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1 + 3); + addCard(Zone.BATTLEFIELD, playerA, bear, 1); + addCard(Zone.HAND, playerA, accident); + + setModeChoice(playerA, "1"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, accident, bear); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerA, bear, 1); + assertPermanentCount(playerA, "Mercenary Token", 0); + assertTappedCount("Swamp", true, 1 + 3); + } + + @Test + public void testSecondMode() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1 + 1); + addCard(Zone.HAND, playerA, accident); + + setModeChoice(playerA, "2"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, accident); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Mercenary Token", 1); + assertTappedCount("Swamp", true, 1 + 1); + } + + @Test + public void testBothModes() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1 + 3 + 1); + addCard(Zone.BATTLEFIELD, playerA, bear, 1); + addCard(Zone.HAND, playerA, accident); + + setModeChoice(playerA, "1"); + setModeChoice(playerA, "2"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, accident, bear); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerA, bear, 1); + assertPermanentCount(playerA, "Mercenary Token", 1); + assertTappedCount("Swamp", true, 1 + 3 + 1); + } + + private static final String electromancer = "Goblin Electromancer"; + + @Test + public void testReduction() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1 + 3 + 1 - 1); + addCard(Zone.BATTLEFIELD, playerA, bear, 1); + addCard(Zone.BATTLEFIELD, playerA, electromancer, 1); + addCard(Zone.HAND, playerA, accident); + + setModeChoice(playerA, "1"); + setModeChoice(playerA, "2"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, accident, bear); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerA, bear, 1); + assertPermanentCount(playerA, "Mercenary Token", 1); + assertTappedCount("Swamp", true, 1 + 3 + 1 - 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/damage/SpitefulShadowsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/damage/SpitefulShadowsTest.java index 4eaffcbd2b7..35473d2a8a6 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/damage/SpitefulShadowsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/damage/SpitefulShadowsTest.java @@ -16,7 +16,7 @@ import org.mage.test.serverside.base.CardTestPlayerBase; public class SpitefulShadowsTest extends CardTestPlayerBase { @Test - public void testCard() { + public void SpitefulShadowsPoisonTest() { // Infect (This creature deals damage to creatures in the form of -1/-1 counters and to players in the form of poison counters.) addCard(Zone.BATTLEFIELD, playerA, "Glistener Elf"); addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); @@ -32,13 +32,13 @@ public class SpitefulShadowsTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.END_TURN); execute(); - assertLife(playerA, 20); - assertLife(playerB, 20); + assertLife(playerA, currentGame.getStartingLife()); + assertLife(playerB, currentGame.getStartingLife()); assertCounterCount(playerA, CounterType.POISON, 3); } @Test - public void testCard1() { + public void SpitefulShadowsRegularTest() { addCard(Zone.BATTLEFIELD, playerA, "Craw Wurm"); // Creature 6/4 addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); @@ -53,9 +53,42 @@ public class SpitefulShadowsTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.END_TURN); execute(); - assertLife(playerA, 17); - assertLife(playerB, 20); + assertLife(playerA, currentGame.getStartingLife() - 3); + assertLife(playerB, currentGame.getStartingLife()); assertCounterCount(playerA, CounterType.POISON, 0); } + @Test + public void SpitefulShadowsMultiDamageTest() { + addCard(Zone.BATTLEFIELD, playerA, "Craw Wurm"); // Creature 6/4 + + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.BATTLEFIELD, playerB, "Memnite", 1); + addCard(Zone.BATTLEFIELD, playerB, "Agent of Stromgald", 1); + + // Whenever an opponent is dealt noncombat damage, Chandra’s Spitfire gets +3/+0 until end of turn. + addCard(Zone.BATTLEFIELD, playerB, "Chandra's Spitfire", 1); + + // Enchant creature + // Whenever enchanted creature is dealt damage, it deals that much damage to its controller. + addCard(Zone.HAND, playerA, "Spiteful Shadows"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Spiteful Shadows", "Craw Wurm"); + + attack(1, playerA, "Craw Wurm", playerB); + block(1, playerB, "Memnite", "Craw Wurm"); + block(1, playerB, "Agent of Stromgald", "Craw Wurm"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerA, currentGame.getStartingLife() - 2); + assertLife(playerB, currentGame.getStartingLife()); + assertCounterCount(playerA, CounterType.POISON, 0); + + // Since Spiteful Shadows should have only triggered once, so should have chandra's spitfire. + assertPowerToughness(playerB, "Chandra's Spitfire", 4, 3); + } + } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/CastFromGraveyardOnceTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/CastFromGraveyardOnceTest.java new file mode 100644 index 00000000000..125d65438b2 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/CastFromGraveyardOnceTest.java @@ -0,0 +1,148 @@ +package org.mage.test.cards.abilities.other; + +import mage.abilities.keyword.IndestructibleAbility; +import mage.constants.EmptyNames; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author xenohedron + */ +public class CastFromGraveyardOnceTest extends CardTestPlayerBase { + + private static final String danitha = "Danitha, New Benalia's Light"; // 2/2 + // Vigilance, trample, lifelink + // Once during each of your turns, you may cast an Aura or Equipment spell from your graveyard. + + private static final String bonesplitter = "Bonesplitter"; // 1 mana equip 1 for +2/+0 + private static final String kitesail = "Kitesail"; // 2 mana equip 2 for +1/+0 and flying + private static final String creature = "Field Creeper"; // 2 mana 2/1 + private static final String halvar = "Halvar, God of Battle"; // MDFC front side - creature 2WW + private static final String sword = "Sword of the Realms"; // MDFC back side - equipment 1W + private static final String auraMorph = "Gift of Doom"; // 4B enchant creature, or morph + private static final String saguArcher = "Sagu Archer"; // 4G 2/5 reach, or morph + private static final String unicorn = "Lonesome Unicorn"; // 4W 3/3 with adventure 2W 2/2 token + private static final String rider = "Rider in Need"; + + private static final String karador = "Karador, Ghost Chieftain"; + // Once during each of your turns, you may cast a creature spell from your graveyard. + + @Test + public void testDanithaAllowsOneCast() { + addCard(Zone.BATTLEFIELD, playerA, danitha); + addCard(Zone.GRAVEYARD, playerA, bonesplitter); + addCard(Zone.GRAVEYARD, playerA, kitesail); + addCard(Zone.GRAVEYARD, playerA, creature); + addCard(Zone.BATTLEFIELD, playerA, "Wastes", 3); + addCard(Zone.BATTLEFIELD, playerA, "Raff Capashen, Ship's Mage"); // historic spells have flash + + checkPlayableAbility("bonesplitter your turn", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + bonesplitter, true); + checkPlayableAbility("kitesail your turn", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + kitesail, true); + checkPlayableAbility("creature not permitted", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + creature, false); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, kitesail); + + checkPermanentCount("kitesail on battlefield", 1, PhaseStep.BEGIN_COMBAT, playerA, kitesail, 1); + checkPlayableAbility("no second cast", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast " + bonesplitter, false); + + checkPlayableAbility("not during opponent's turn", 2, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + bonesplitter, false); + + checkPlayableAbility("available next turn", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + bonesplitter, true); + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, bonesplitter); + + setStrictChooseMode(true); + setStopAt(3, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, bonesplitter, 1); + } + + @Test + public void testDanithaMDFC() { + addCard(Zone.BATTLEFIELD, playerA, danitha); + addCard(Zone.GRAVEYARD, playerA, halvar); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 4); + + checkPlayableAbility("front not allowed", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + halvar, false); + checkPlayableAbility("back allowed", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + sword, true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, sword); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, sword, 1); + } + + @Test + public void testDanithaMorph() { + addCard(Zone.BATTLEFIELD, playerA, danitha); + addCard(Zone.GRAVEYARD, playerA, auraMorph); + addCard(Zone.BATTLEFIELD, playerA, creature); // to enchant + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 5); + + checkPlayableAbility("morph not allowed", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + auraMorph + " using Morph", false); + checkPlayableAbility("aura allowed", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + auraMorph, true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, auraMorph, creature); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, auraMorph, 1); + assertAbility(playerA, creature, IndestructibleAbility.getInstance(), true); + } + + @Test + public void testKaradorCastWithoutMorph() { + addCard(Zone.BATTLEFIELD, playerA, karador); + addCard(Zone.GRAVEYARD, playerA, saguArcher); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 5); + + checkPlayableAbility("with morph", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + saguArcher + " using Morph", true); + checkPlayableAbility("without morph", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + saguArcher, true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, saguArcher); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, saguArcher, 1); + } + + @Test + public void testKaradorCastWithMorph() { + addCard(Zone.BATTLEFIELD, playerA, karador); + addCard(Zone.GRAVEYARD, playerA, saguArcher); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 5); + + checkPlayableAbility("with morph", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + saguArcher + " using Morph", true); + checkPlayableAbility("without morph", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + saguArcher, true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, saguArcher + " using Morph"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + } + + @Test + public void testKaradorCastAdventure() { + addCard(Zone.BATTLEFIELD, playerA, karador); + addCard(Zone.GRAVEYARD, playerA, unicorn); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 5); + + checkPlayableAbility("creature", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + unicorn, true); + checkPlayableAbility("sorcery", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + rider, false); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, unicorn); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, unicorn, 1); + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/MultipleAsThoughEffects.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/MultipleAsThoughEffectsTest.java similarity index 87% rename from Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/MultipleAsThoughEffects.java rename to Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/MultipleAsThoughEffectsTest.java index c8d72b56cec..8a52fb685a5 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/MultipleAsThoughEffects.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/MultipleAsThoughEffectsTest.java @@ -8,16 +8,16 @@ import org.mage.test.serverside.base.CardTestPlayerBase; /** * @author Alex-Vasile, Susucr */ -public class MultipleAsThoughEffects extends CardTestPlayerBase { +public class MultipleAsThoughEffectsTest extends CardTestPlayerBase { /** - * Reported bug: https://github.com/magefree/mage/issues/8584 - * + * Reported bug #8584 + *

* If there are multiple effects which allow a player to cast a spell, - * they should be able to choose which one they whish to use. + * they should be able to choose which one they wish to use. */ @Test - public void ChoosingAlternateCastingMethod() { + public void testChoosingAlternateCastingMethod() { setStrictChooseMode(true); skipInitShuffling(); @@ -51,13 +51,13 @@ public class MultipleAsThoughEffects extends CardTestPlayerBase { } /** - * Reported bug: https://github.com/magefree/mage/issues/2087 - * + * Reported bug: #2087 + *

* If there are multiple effects which allow a player to cast a spell, - * they should be able to choose which one they whish to use, even if one is single-use. + * they should be able to choose which one they wish to use, even if one is single-use. */ @Test - public void RisenExecutioner() { + public void testRisenExecutioner() { setStrictChooseMode(true); // You may cast Risen Executioner from your graveyard if you pay {1} more to cast it for each other creature card in your graveyard. @@ -78,4 +78,4 @@ public class MultipleAsThoughEffects extends CardTestPlayerBase { assertPermanentCount(playerA, "Risen Executioner", 2); assertTappedCount("Swamp", true, 4 + 5); } -} \ No newline at end of file +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/control/VengefulPharaohTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/control/VengefulPharaohTest.java index 0e0ae36cb51..dd123fc39b7 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/control/VengefulPharaohTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/control/VengefulPharaohTest.java @@ -22,6 +22,8 @@ public class VengefulPharaohTest extends CardTestPlayerBase { // Destroy target permanent. addCard(Zone.HAND, playerA, "Vindicate", 1); // Sorcery {1}{W}{B} + // Enchant creature + // You control enchanted creature. addCard(Zone.HAND, playerB, "Control Magic", 1); // Enchantment addCard(Zone.BATTLEFIELD, playerB, "Island", 4); addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1); @@ -45,4 +47,57 @@ public class VengefulPharaohTest extends CardTestPlayerBase { assertLife(playerA, 18); } + + @Test + public void doubleTriggerTest() { + // If multiple creatures deal combat damage to you and to a planeswalker you control simultaneously, + // Vengeful Pharaoh will trigger twice. The first trigger will cause Vengeful Pharaoh to be put on top of your + // library. The second trigger will then do nothing, as Vengeful Pharaoh is no longer in your graveyard when it + // tries to resolve. Note that the second trigger will do nothing even if Vengeful Pharaoh is put back into + // your graveyard before it tries to resolve, as it’s a different Vengeful Pharaoh than the one that was there + // before. (2011-09-22) + + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + + // Deathtouch (Any amount of damage this deals to a creature is enough to destroy it.) + // Whenever combat damage is dealt to you or a planeswalker you control, if Vengeful Pharaoh is in your + // graveyard, destroy target attacking creature, then put Vengeful Pharaoh on top of your library. + addCard(Zone.BATTLEFIELD, playerA, "Vengeful Pharaoh", 1); // Creature 5/4 {2}{B}{B}{B} + + // Legendary Planeswalker — Tibalt {R}{R} + // +1: Draw a card, then discard a card at random. + // −4: Tibalt, the Fiend-Blooded deals damage equal to the number of cards in target player’s hand to that player. + // −6: Gain control of all creatures until end of turn. Untap them. They gain haste until end of turn. + addCard(Zone.BATTLEFIELD, playerA, "Tibalt, the Fiend-Blooded", 1); + + // Destroy target permanent. + addCard(Zone.HAND, playerA, "Vindicate", 1); // Sorcery {1}{W}{B} + + // 2/1 + addCard(Zone.BATTLEFIELD, playerB, "Expedition Envoy", 1); + + // 2/2 + addCard(Zone.BATTLEFIELD, playerB, "Grizzly Bears", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Vindicate", "Vengeful Pharaoh"); + + attack(2, playerB, "Expedition Envoy", playerA); + attack(2, playerB, "Grizzly Bears", "Tibalt, the Fiend-Blooded"); + + // choose trigger to go on the stack first + setChoice(playerA, "Whenever combat damage"); + + addTarget(playerA, "Expedition Envoy"); + addTarget(playerA, "Grizzly Bears"); + + setStrictChooseMode(true); + setStopAt(2, PhaseStep.END_TURN); + execute(); + + assertLife(playerA, currentGame.getStartingLife() - 2); + assertLibraryCount(playerA, "Vengeful Pharaoh", 1); + assertGraveyardCount(playerB, "Grizzly Bears", 1); + assertPermanentCount(playerB, "Expedition Envoy", 1); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/copy/CleverImpersonatorTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/copy/CleverImpersonatorTest.java index a390ef48ff2..6d18f7535e4 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/copy/CleverImpersonatorTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/copy/CleverImpersonatorTest.java @@ -123,14 +123,23 @@ public class CleverImpersonatorTest extends CardTestPlayerBase { } /** - * So I copied Jace, Vryns Prodigy with Clever Impersonator (it was tapped - * and I needed a blocker for a token...), and Jace got to survive until the - * next turn. When I looted, he flipped, and I got an error message I - * couldn't get rid of, forcing me to concede. I'm not sure what the correct - * outcome is rules-wise. + * 712.14a. If a spell or ability puts a transforming double-faced card onto the battlefield "transformed" + * or "converted," it enters the battlefield with its back face up. If a player is instructed to put a card + * that isn't a transforming double-faced card onto the battlefield transformed or converted, that card stays in + * its current zone. + *

+ * * So I copied Jace, Vryns Prodigy with Clever Impersonator (it was tapped + * * and I needed a blocker for a token...), and Jace got to survive until the + * * next turn. When I looted, he flipped, and I got an error message I + * * couldn't get rid of, forcing me to concede. I'm not sure what the correct + * * outcome is rules-wise. */ @Test public void testCopyCreatureOfFlipPlaneswalker() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.LIBRARY, playerA, "Swamp"); // for discard addCard(Zone.BATTLEFIELD, playerA, "Island", 4); // {T}: Draw a card, then discard a card. If there are five or more cards in your graveyard, exile Jace, Vryn's Prodigy, then return him to the battefield transformed under his owner's control. @@ -143,23 +152,24 @@ public class CleverImpersonatorTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerB, "Pillarfield Ox", 1); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Clever Impersonator"); - setChoice(playerA, "Jace, Vryn's Prodigy"); - setChoice(playerA, "Jace, Vryn's Prodigy[only copy]"); // keep the copied Jace + setChoice(playerA, true); // yes to copy + setChoice(playerA, "Jace, Vryn's Prodigy"); // copy Jace + setChoice(playerA, "Jace, Vryn's Prodigy[only copy]"); // keep the copied Jace under legend rule activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Draw a card"); - setChoice(playerA, "Pillarfield Ox"); + setChoice(playerA, "Swamp"); setStopAt(3, PhaseStep.BEGIN_COMBAT); execute(); assertGraveyardCount(playerA, "Jace, Vryn's Prodigy", 1); - assertPermanentCount(playerA, "Pillarfield Ox", 1); - + assertPermanentCount(playerA, "Pillarfield Ox", 0); + assertExileCount(playerA, "Clever Impersonator", 1); // Clone does not come back as per 712.14a.. } /** * Reported bug: - * Could not use Clever Impersonator to copy Dawn's Reflection + * Could not use Clever Impersonator to copy Dawn's Reflection */ @Test public void dawnsReflectionCopiedByImpersonator() { 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 659c4bdb6be..7b267a5f052 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 @@ -21,6 +21,7 @@ public class CollectEvidenceTest extends CardTestPlayerBase { private static final String effigy = "Fuming Effigy"; private static final String sprite = "Crimestopper Sprite"; private static final String monitor = "Surveillance Monitor"; + private static final String unraveler = "Conspiracy Unraveler"; @Test public void testNoPay() { @@ -291,4 +292,22 @@ public class CollectEvidenceTest extends CardTestPlayerBase { assertTapped(sprite, true); assertCounterCount(sprite, CounterType.STUN, 1); } + + @Test + public void testConspiracyUnraveler() { + addCard(Zone.BATTLEFIELD, playerA, unraveler); + addCard(Zone.HAND, playerA, "Colossal Dreadmaw"); + addCard(Zone.GRAVEYARD, playerA, ogre, 4); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Colossal Dreadmaw"); + setChoice(playerA, true); // use alternative cast from unraveler + setChoice(playerA, ogre, 4); // pay for collect evidence + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertExileCount(ogre, 4); + assertPermanentCount(playerA, "Colossal Dreadmaw", 1); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/damage/ExcessDamageTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/damage/ExcessDamageTest.java index e9a069e270a..831b6f77dd8 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/damage/ExcessDamageTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/damage/ExcessDamageTest.java @@ -12,6 +12,14 @@ public class ExcessDamageTest extends CardTestPlayerBase { private static final String spill = "Flame Spill"; private static final String bear = "Grizzly Bears"; + private static final String envoy = "Expedition Envoy"; + + //Bonded Construct can’t attack alone. + private static final String bondedConstruct = "Bonded Construct"; + + // Spend only mana produced by creatures to cast this spell. + private static final String myrSuperion = "Myr Superion"; + private static final String jab = "Flame Jab"; private static final String spirit = "Pestilent Spirit"; private static final String myr = "Darksteel Myr"; @@ -131,6 +139,44 @@ public class ExcessDamageTest extends CardTestPlayerBase { assertGraveyardCount(playerB, bear, 1); } + @Test + public void testAegarTheFreezingFlameMultiDamage() { + + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + + addCard(Zone.BATTLEFIELD, playerA, aegar); + addCard(Zone.BATTLEFIELD, playerA, bear); + addCard(Zone.BATTLEFIELD, playerA, envoy); + addCard(Zone.BATTLEFIELD, playerA, bondedConstruct); + addCard(Zone.HAND, playerA, bolt); + + addCard(Zone.BATTLEFIELD, playerB, myrSuperion); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, bolt, myrSuperion, true); + + attack(2, playerB, myrSuperion, playerA); + block(2, playerA, bear, myrSuperion); + block(2, playerA, envoy, myrSuperion); + block(2, playerA, bondedConstruct, myrSuperion); + + //Assign this much damage to the first blocking creature + setChoice(playerB, "X=2"); + + //Assign this much damage to the second blocking creature + setChoice(playerB, "X=1"); + + //Assign this much damage to the third blocking creature + setChoice(playerB, "X=1"); + + setStrictChooseMode(true); + setStopAt(2, PhaseStep.END_TURN); + execute(); + + // Excess damage was dealt by the total of 3 blocking creatures, 2 of which each dealt more than lethal damage, + // but Aegar's ability should only trigger once + assertHandCount(playerA, 1); + } + @Test public void testMagmaticGalleon() { String mg = "Magmatic Galleon"; diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/avr/OutwitTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/avr/OutwitTest.java new file mode 100644 index 00000000000..e74447912d6 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/avr/OutwitTest.java @@ -0,0 +1,60 @@ +package org.mage.test.cards.single.avr; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class OutwitTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.o.Outwit Outwit} {U} + * Instant + * Counter target spell that targets a player. + */ + private static final String outwit = "Outwit"; + + @Test + public void test_BoltTargettingPlayer() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerB, "Island", 1); + addCard(Zone.HAND, playerB, outwit); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + addCard(Zone.HAND, playerA, "Lightning Bolt"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB); + checkPlayableAbility("Outwit castable", 1, PhaseStep.PRECOMBAT_MAIN, playerB, "Cast Outwit", true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, outwit, "Lightning Bolt"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertLife(playerB, 20); + assertGraveyardCount(playerA, "Lightning Bolt", 1); + assertGraveyardCount(playerB, outwit, 1); + } + + @Test + public void test_BoltTargettingCreature_CantCastOutwit() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerB, "Island", 1); + addCard(Zone.HAND, playerB, outwit); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + addCard(Zone.HAND, playerA, "Lightning Bolt"); + addCard(Zone.BATTLEFIELD, playerA, "Memnite"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "Memnite"); + checkPlayableAbility("Outwit not castable without valid target", 1, PhaseStep.PRECOMBAT_MAIN, playerB, "Cast Outwit", false); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Memnite", 1); + assertGraveyardCount(playerA, "Lightning Bolt", 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/big/TerritoryForgeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/big/TerritoryForgeTest.java new file mode 100644 index 00000000000..b7564a07b58 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/big/TerritoryForgeTest.java @@ -0,0 +1,41 @@ +package org.mage.test.cards.single.big; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class TerritoryForgeTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.t.TerritoryForge Territory Forge} {4}{R} + * Artifact + * When Territory Forge enters the battlefield, if you cast it, exile target artifact or land. + * Territory Forge has all activated abilities of the exiled card. + */ + private static final String forge = "Territory Forge"; + + @Test + public void test_Simple() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerB, "Braidwood Cup"); // {T}: You gain 1 life. + addCard(Zone.HAND, playerA, forge); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, forge); + addTarget(playerA, "Braidwood Cup"); + + checkExileCount("After etb, Cup in exile", 1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Braidwood Cup", 1); + activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{T}"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerA, 20 + 1); + assertTapped(forge, true); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/c17/KessDissidentMageTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/c17/KessDissidentMageTest.java index bbd572d20f9..36684010cc0 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/c17/KessDissidentMageTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/c17/KessDissidentMageTest.java @@ -2,6 +2,7 @@ package org.mage.test.cards.single.c17; import mage.constants.PhaseStep; import mage.constants.Zone; +import org.junit.Ignore; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; @@ -97,4 +98,68 @@ public class KessDissidentMageTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.END_TURN); execute(); } + + private static final String unicorn = "Lonesome Unicorn"; // 4W 3/3 with adventure 2W 2/2 token + private static final String rider = "Rider in Need"; + private static final String lifegain = "Chaplain's Blessing"; + private static final String kess = "Kess, Dissident Mage"; + // Once during each of your turns, you may cast an instant or sorcery spell from your graveyard. + // If a spell cast this way would be put into your graveyard, exile it instead. + + @Test + public void testKessCastAdventure() { + addCard(Zone.BATTLEFIELD, playerA, kess); + addCard(Zone.GRAVEYARD, playerA, lifegain); + addCard(Zone.GRAVEYARD, playerA, unicorn); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 6); + + checkPlayableAbility("lifegain", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + lifegain, true); + checkPlayableAbility("creature", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + unicorn, false); + checkPlayableAbility("adventure", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + rider, true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, rider); + + checkPlayableAbility("already used", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast " + lifegain, false); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Knight Token", 1); + assertExileCount(playerA, unicorn, 1); + } + + @Test + @Ignore("failing, see issue #11924") + public void testKessCastAdventureAfterDeath() { + addCard(Zone.BATTLEFIELD, playerA, kess); + addCard(Zone.GRAVEYARD, playerA, lifegain); + addCard(Zone.HAND, playerA, unicorn); + addCard(Zone.BATTLEFIELD, playerA, "Blood Bairn"); // for sacrificing creature + addCard(Zone.BATTLEFIELD, playerA, "Plains", 6); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, unicorn); + + checkPlayableAbility("lifegain", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast " + lifegain, true); + checkPlayableAbility("creature", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast " + unicorn, false); + checkPlayableAbility("adventure", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast " + rider, false); + activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Sacrifice another"); + setChoice(playerA, unicorn); + + checkGraveyardCount("sacrificed", 2, PhaseStep.PRECOMBAT_MAIN, playerA, unicorn, 1); + + checkPlayableAbility("lifegain", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + lifegain, true); + checkPlayableAbility("creature", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + unicorn, false); + checkPlayableAbility("adventure", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + rider, true); + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, rider); + + checkPlayableAbility("already used", 3, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast " + lifegain, false); + + setStrictChooseMode(true); + setStopAt(3, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Knight Token", 1); + assertExileCount(playerA, unicorn, 1); + } + } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/c21/LaeliaTheBladeReforgedTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/c21/LaeliaTheBladeReforgedTest.java new file mode 100644 index 00000000000..ec20ecc3e23 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/c21/LaeliaTheBladeReforgedTest.java @@ -0,0 +1,131 @@ +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; + +/** + * {@link mage.cards.l.LaeliaTheBladeReforged Laelia, the Blade Reforged} + * {2}{R} + * Legendary Creature - Spirit Warrior + * Haste + * Whenever Laelia, the Blade Reforged attacks, exile the top card of your library. You may play that card this turn. + * Whenever one or more cards are put into exile from your library and/or your graveyard, put a +1/+1 counter on Laelia. + * + * @author DominionSpy + */ +public class LaeliaTheBladeReforgedTest extends CardTestPlayerBase { + + private static final String laelia = "Laelia, the Blade Reforged"; + private static final String cranialExtraction = "Cranial Extraction"; + private static final String llanowarElves = "Llanowar Elves"; + + @Test + public void controllerExilesOwnCards() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4); + addCard(Zone.BATTLEFIELD, playerA, laelia); + addCard(Zone.HAND, playerA, cranialExtraction); + addCard(Zone.HAND, playerA, llanowarElves); + addCard(Zone.GRAVEYARD, playerA, llanowarElves); + addCard(Zone.LIBRARY, playerA, llanowarElves); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, cranialExtraction, playerA); + // Choose a nonland card name (Llanowar Elves) + setChoice(playerA, llanowarElves); + // Graveyard + setChoice(playerA, llanowarElves); + // Hand + setChoice(playerA, llanowarElves); + // Library + setChoice(playerA, llanowarElves); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertCounterCount(playerA, laelia, CounterType.P1P1, 1); + } + + @Test + public void opponentExilesControllersCards() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4); + addCard(Zone.HAND, playerA, cranialExtraction); + + addCard(Zone.BATTLEFIELD, playerB, laelia); + addCard(Zone.HAND, playerB, llanowarElves); + addCard(Zone.GRAVEYARD, playerB, llanowarElves); + addCard(Zone.LIBRARY, playerB, llanowarElves); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, cranialExtraction, playerB); + // Choose a nonland card name (Llanowar Elves) + setChoice(playerA, llanowarElves); + // Graveyard + setChoice(playerA, llanowarElves); + // Hand + setChoice(playerA, llanowarElves); + // Library + setChoice(playerA, llanowarElves); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertCounterCount(playerB, laelia, CounterType.P1P1, 1); + } + + @Test + public void controllerExilesOpponentsCards() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4); + addCard(Zone.BATTLEFIELD, playerA, laelia); + addCard(Zone.HAND, playerA, cranialExtraction); + + addCard(Zone.HAND, playerB, llanowarElves); + addCard(Zone.GRAVEYARD, playerB, llanowarElves); + addCard(Zone.LIBRARY, playerB, llanowarElves); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, cranialExtraction, playerB); + // Choose a nonland card name (Llanowar Elves) + setChoice(playerA, llanowarElves); + // Graveyard + setChoice(playerA, llanowarElves); + // Hand + setChoice(playerA, llanowarElves); + // Library + setChoice(playerA, llanowarElves); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertCounterCount(playerA, laelia, CounterType.P1P1, 0); + } + + @Test + public void opponentExilesOwnCards() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4); + addCard(Zone.HAND, playerA, cranialExtraction); + addCard(Zone.HAND, playerA, llanowarElves); + addCard(Zone.GRAVEYARD, playerA, llanowarElves); + addCard(Zone.LIBRARY, playerA, llanowarElves); + + addCard(Zone.BATTLEFIELD, playerB, laelia); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, cranialExtraction, playerA); + // Choose a nonland card name (Llanowar Elves) + setChoice(playerA, llanowarElves); + // Graveyard + setChoice(playerA, llanowarElves); + // Hand + setChoice(playerA, llanowarElves); + // Library + setChoice(playerA, llanowarElves); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertCounterCount(playerB, laelia, CounterType.P1P1, 0); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/clb/GaleWaterdeepProdigyTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/clb/GaleWaterdeepProdigyTest.java index 3991a19fdd0..9b346084975 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/clb/GaleWaterdeepProdigyTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/clb/GaleWaterdeepProdigyTest.java @@ -6,12 +6,18 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** + * {@link mage.cards.g.GaleWaterdeepProdigy Gale, Waterdeep Prodigy} {2}{U} + * Legendary Creature — Human Wizard + * Whenever you cast an instant or sorcery spell from your hand, you may cast up to one target card of the other type from your graveyard. If a spell cast from your graveyard this way would be put into your graveyard, exile it instead. + * Choose a Background (You can have a Background as a second commander.) + * 1/3 + * * @author Rjayz */ public class GaleWaterdeepProdigyTest extends CardTestPlayerBase { @Test - public void TestGaleWaterDeepProdigy() { + public void test_GaleWaterDeepProdigy() { addCard(Zone.BATTLEFIELD, playerA, "Island", 10); addCard(Zone.BATTLEFIELD, playerA, "Mountain", 10); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/dmu/BriarHydraTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/dmu/BriarHydraTest.java new file mode 100644 index 00000000000..0576b42656b --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/dmu/BriarHydraTest.java @@ -0,0 +1,58 @@ +package org.mage.test.cards.single.dmu; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class BriarHydraTest extends CardTestPlayerBase { + + private static final String thranPortal = "Thran Portal"; + + /** + * {@link mage.cards.b.BriarHydra Briar Hydra} {5}{G} + * Creature — Plant Hydra + * Trample + * Domain — Whenever Briar Hydra deals combat damage to a player, put X +1/+1 counters on target creature you control, where X is the number of basic land types among lands you control. + * 6/6 + */ + private static final String hydra = "Briar Hydra"; + + /** + * There was a bug when the Domain value was 0, the Hydra would incorrectly + * add a +1/+1 counter to the target. + */ + @Test + public void test_NoDomain() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, hydra); + + attack(1, playerA, hydra, playerB); + addTarget(playerA, hydra); // trigger does target + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPowerToughness(playerA, hydra, 6, 6); + } + + @Test + public void test_Domain_2() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, hydra); + addCard(Zone.BATTLEFIELD, playerA, "Bayou"); + + attack(1, playerA, hydra, playerB); + addTarget(playerA, hydra); // trigger does target + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPowerToughness(playerA, hydra, 6 + 2, 6 + 2); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/fdn/RiteOfPassageTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/fdn/RiteOfPassageTest.java index 2ae8ccec098..6f1035cc025 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/fdn/RiteOfPassageTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/fdn/RiteOfPassageTest.java @@ -25,6 +25,33 @@ public class RiteOfPassageTest extends CardTestPlayerBase { assertCounterCount("Watchwolf", CounterType.P1P1, 1); } + @Test + public void addCounterMultiDamage(){ + // Watchwolf 3/3 + addCard(Zone.BATTLEFIELD, playerA, "Watchwolf", 1); + // Whenever a creature you control is dealt damage, put a +1/+1 counter on it. + addCard(Zone.BATTLEFIELD, playerA, "Rite of Passage"); + + addCard(Zone.BATTLEFIELD, playerB, "Memnite", 1); + addCard(Zone.BATTLEFIELD, playerB, "Agent of Stromgald", 1); + + attack(1, playerA, "Watchwolf", playerB); + block(1, playerB, "Memnite", "Watchwolf"); + block(1, playerB, "Agent of Stromgald", "Watchwolf"); + + // Assign this much damage to Memnite + setChoice(playerA, "X=1"); + + // Assign this much damage to Agent of Stromgald + setChoice(playerA, "X=1"); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertCounterCount("Watchwolf", CounterType.P1P1, 1); + } + @Test public void addCounterNonLethalOppControlsVorinclex(){ // Watchwolf 3/3 diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/OjerAxonilDeepestMightTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/OjerAxonilDeepestMightTest.java index 117df1b3b7e..b5601a7cc66 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/OjerAxonilDeepestMightTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/OjerAxonilDeepestMightTest.java @@ -66,6 +66,26 @@ public class OjerAxonilDeepestMightTest extends CardTestPlayerBase { assertLife(playerB, 20 - 4); } + /** + * bug report: red sources from others players are affected. + * #12066 + */ + @Test + public void testReplacement_NotControlled() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, ojer, 1); + addCard(Zone.HAND, playerB, bolt, 1); + addCard(Zone.BATTLEFIELD, playerB, "Mountain", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, bolt, playerB); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertLife(playerB, 20 - 3); // Ojer doesn't modify that damage + } + @Test public void testReplacement_BoltOwnFace() { setStrictChooseMode(true); @@ -192,6 +212,37 @@ public class OjerAxonilDeepestMightTest extends CardTestPlayerBase { assertTapped(ojer, true); } + /** + * 712.14a. If a spell or ability puts a transforming double-faced card onto the battlefield "transformed" + * or "converted," it enters the battlefield with its back face up. If a player is instructed to put a card + * that isn't a transforming double-faced card onto the battlefield transformed or converted, that card stays in + * its current zone. + */ + @Test + public void test_CloneDoNotTransform() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, ojer, 1); + addCard(Zone.HAND, playerA, "Sakashima the Impostor", 1); // Clone keeping its name for easier test. + addCard(Zone.BATTLEFIELD, playerA, "Underground Sea", 6); + addCard(Zone.HAND, playerA, "Doom Blade", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sakashima the Impostor"); + setChoice(playerA, true); // yes to clone + setChoice(playerA, ojer); // clone Ojer + + checkPermanentCount("Sakashima in play", 1, PhaseStep.BEGIN_COMBAT, playerA, "Sakashima the Impostor", 1); + checkPT("PT 4/4 so copy happened", 1, PhaseStep.BEGIN_COMBAT, playerA, "Sakashima the Impostor", 4, 4); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Doom Blade", "Sakashima the Impostor"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, ojer, 1); + assertGraveyardCount(playerA, "Sakashima the Impostor", 1); // is not transformable, so didn't return. + } + @Test public void test_watching_Chandra_emblem_damage() { setStrictChooseMode(true); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/ShipwreckSentryTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/ShipwreckSentryTest.java new file mode 100644 index 00000000000..9f4bead5a3b --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/ShipwreckSentryTest.java @@ -0,0 +1,61 @@ +package org.mage.test.cards.single.lci; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author xenohedron + */ +public class ShipwreckSentryTest extends CardTestPlayerBase { + + /** + Shipwreck Sentry {1}{U} + Creature — Human Pirate + Defender + As long as an artifact entered the battlefield under your control this turn, Shipwreck Sentry can attack as though it didn’t have defender. + */ + private static final String sentry = "Shipwreck Sentry"; + + // {0} Artifact + private static final String relic = "Darksteel Relic"; + + @Test + public void testCantAttack() { + addCard(Zone.BATTLEFIELD, playerA, sentry); + addCard(Zone.HAND, playerA, relic); + + attack(1, playerA, sentry, playerB); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + try { + execute(); + } catch (AssertionError e) { + Assert.assertTrue("No artifact entered", + e.getMessage().contains("Can't find available command - attack:Shipwreck Sentry$defendingPlayer=PlayerB")); + } + + } + + @Test + public void testCanAttack() { + addCard(Zone.BATTLEFIELD, playerA, sentry); + addCard(Zone.HAND, playerA, relic); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, relic); + attack(1, playerA, sentry, playerB); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, relic, 1); + assertTappedCount(sentry, true, 1); + assertLife(playerB, 17); + + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/ltr/SavvyTraderTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/ltr/SavvyTraderTest.java new file mode 100644 index 00000000000..35d5ff706a7 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/ltr/SavvyTraderTest.java @@ -0,0 +1,65 @@ +package org.mage.test.cards.single.ltr; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class SavvyTraderTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.s.SavvyTrader Savvy Trader} + * Savvy Trader {3}{G} + * Creature — Human Citizen + * When Savvy Trader enters the battlefield, exile target permanent card from your graveyard. You may play that card for as long as it remains exiled. + * Spells you cast from anywhere other than your hand cost {1} less to cast. + * 3/3 + */ + private static final String trader = "Savvy Trader"; + + @Test + public void test_Play_Baneslayer() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, trader, 1); + addCard(Zone.GRAVEYARD, playerA, "Baneslayer Angel", 1); + addCard(Zone.BATTLEFIELD, playerA, "Savannah", 4); // 4 is enough to pay for Angel with trader reduction. + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, trader); + addTarget(playerA, "Baneslayer Angel"); + + checkExileCount("Angel got exiled", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Baneslayer Angel", 1); + + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Baneslayer Angel"); + + setStopAt(3, PhaseStep.BEGIN_COMBAT); + execute(); + + assertTappedCount("Savannah", true, 4); + assertPermanentCount(playerA, "Baneslayer Angel", 1); + } + + @Test + public void test_Play_Swamp() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, trader, 1); + addCard(Zone.GRAVEYARD, playerA, "Swamp", 1); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 4); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, trader); + addTarget(playerA, "Swamp"); + + checkExileCount("Swamp got exiled", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Swamp", 1); + + playLand(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Swamp"); + + setStopAt(3, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Swamp", 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mbs/GalvanothTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mbs/GalvanothTest.java new file mode 100644 index 00000000000..303a72339a0 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mbs/GalvanothTest.java @@ -0,0 +1,71 @@ +package org.mage.test.cards.single.mbs; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class GalvanothTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.g.Galvanoth Galvanoth} {3}{R}{R} + * Creature — Beast + * At the beginning of your upkeep, you may look at the top card of your library. You may cast it without paying its mana cost if it’s an instant or sorcery spell. + * 3/3 + */ + private static final String galvanoth = "Galvanoth"; + + @Test + public void test_Divination_Cast() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.LIBRARY, playerA, "Divination"); + addCard(Zone.BATTLEFIELD, playerA, galvanoth); + + setChoice(playerA, true); // yes to look + setChoice(playerA, true); // yes to cast + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertGraveyardCount(playerA, "Divination", 1); + assertHandCount(playerA, 2); + } + + @Test + public void test_Divination_No_Cast() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.LIBRARY, playerA, "Divination"); + addCard(Zone.BATTLEFIELD, playerA, galvanoth); + + setChoice(playerA, true); // yes to look + setChoice(playerA, false); // no to cast + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertHandCount(playerA, 0); + } + + @Test + public void test_Creature_NotCastable() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.LIBRARY, playerA, "Goblin Piker"); + addCard(Zone.BATTLEFIELD, playerA, galvanoth); + + setChoice(playerA, true); // yes to look + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, galvanoth, 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/AssembleThePlayersTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/AssembleThePlayersTest.java new file mode 100644 index 00000000000..569eb5850d0 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/AssembleThePlayersTest.java @@ -0,0 +1,55 @@ +package org.mage.test.cards.single.mkm; + +import mage.constants.EmptyNames; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author xenohedron + */ +public class AssembleThePlayersTest extends CardTestPlayerBase { + + /* + Assemble the Players {1}{W} Enchantment + You may look at the top card of your library any time. + Once each turn, you may cast a creature spell with power 2 or less from the top of your library. + */ + private static final String assemble = "Assemble the Players"; + private static final String merfolk = "Coral Merfolk"; + private static final String drake = "Seacoast Drake"; + private static final String glacial = "Glacial Stalker"; + + @Test + public void testNormalAndMorph() { + skipInitShuffling(); + addCard(Zone.BATTLEFIELD, playerA, "Island", 3); + addCard(Zone.BATTLEFIELD, playerA, assemble); + addCard(Zone.LIBRARY, playerA, glacial); + addCard(Zone.LIBRARY, playerA, drake); + addCard(Zone.LIBRARY, playerA, merfolk); + + checkHandCardCount("not in hand", 1, PhaseStep.PRECOMBAT_MAIN, playerA, merfolk, 0); + checkHandCardCount("not in hand", 1, PhaseStep.PRECOMBAT_MAIN, playerA, drake, 0); + checkHandCardCount("not in hand", 1, PhaseStep.PRECOMBAT_MAIN, playerA, glacial, 0); + checkPlayableAbility("from library", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + merfolk, true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, merfolk); + + checkPlayableAbility("once per turn", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + drake, false); + + checkHandCardCount("not in hand", 3, PhaseStep.PRECOMBAT_MAIN, playerA, merfolk, 0); + checkHandCardCount("in hand", 3, PhaseStep.PRECOMBAT_MAIN, playerA, drake, 1); + checkHandCardCount("not in hand", 3, PhaseStep.PRECOMBAT_MAIN, playerA, glacial, 0); + + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, glacial + " using Morph"); + + setStrictChooseMode(true); + setStopAt(3, PhaseStep.END_TURN); + execute(); + + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); + assertPowerToughness(playerA, merfolk, 2, 1); + + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/ProjektorInspectorTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/ProjektorInspectorTest.java new file mode 100644 index 00000000000..04feb3bd06d --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/ProjektorInspectorTest.java @@ -0,0 +1,41 @@ +package org.mage.test.cards.single.mkm; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class ProjektorInspectorTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.p.ProjektorInspector} {U} + * Creature — Human Detective + * Whenever Projektor Inspector or another Detective enters the battlefield under your control and + * whenever a Detective you control is turned face up, you may draw a card. If you do, discard a card. + * 3/2 + */ + private static final String inspector = "Projektor Inspector"; + + @Test + public void test_Trigger_FaceUp() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.LIBRARY, playerA, "Healing Salve"); // for discard + addCard(Zone.BATTLEFIELD, playerA, inspector); + addCard(Zone.HAND, playerA, "Basilica Stalker"); // Disguise {4}{B}, detective + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 8); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Basilica Stalker using Disguise", true); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{4}{B}:"); + setChoice(playerA, true); // yes to loot + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Healing Salve", 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/VojaJawsOfTheConclaveTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/VojaJawsOfTheConclaveTest.java new file mode 100644 index 00000000000..245bda8ec4d --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/VojaJawsOfTheConclaveTest.java @@ -0,0 +1,52 @@ +package org.mage.test.cards.single.mkm; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class VojaJawsOfTheConclaveTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.v.VojaJawsOfTheConclave Voja, Jaws of the Conclave} {2}{R}{G}{W} + * Legendary Creature — Wolf + * Vigilance, trample, ward {3} + * Whenever Voja, Jaws of the Conclave attacks, put X +1/+1 counters on each creature you control, where X is the number of Elves you control. Draw a card for each Wolf you control. + * 5/5 + */ + private static final String voja = "Voja, Jaws of the Conclave"; + + @Test + public void test_NoElves() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, voja); + + attack(1, playerA, voja, playerB); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPowerToughness(playerA, voja, 5, 5); + assertHandCount(playerA, 1); + } + + @Test + public void test_2_Elves() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, voja); + addCard(Zone.BATTLEFIELD, playerA, "Llanowar Elves", 2); + + attack(1, playerA, voja, playerB); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPowerToughness(playerA, voja, 5 + 2, 5 + 2); + assertHandCount(playerA, 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/neo/KairiTheSwirlingSkyTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/neo/KairiTheSwirlingSkyTest.java new file mode 100644 index 00000000000..fb0c2ae867f --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/neo/KairiTheSwirlingSkyTest.java @@ -0,0 +1,88 @@ +package org.mage.test.cards.single.neo; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class KairiTheSwirlingSkyTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.k.KairiTheSwirlingSky Kairi, the Swirling Sky} {4}{U}{U} + * Legendary Creature — Dragon Spirit + * Flying, ward {3} + * When Kairi, the Swirling Sky dies, choose one — + * • Return any number of target nonland permanents with total mana value 6 or less to their owners’ hands. + * • Mill six cards, then return up to two instant and/or sorcery cards from your graveyard to your hand. + * 6/6 + */ + + private static final String kairi = "Kairi, the Swirling Sky"; + + @Test + public void test_Return_MV6() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, kairi); + addCard(Zone.BATTLEFIELD, playerA, "Colossal Dreadmaw"); + addCard(Zone.HAND, playerA, "Doom Blade"); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); + + setModeChoice(playerA, "1"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Doom Blade", kairi); + addTarget(playerA, "Colossal Dreadmaw"); // returning dreadmaw + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertHandCount(playerA, "Colossal Dreadmaw", 1); + } + + @Test + public void test_Return_MV4_MV0() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, kairi); + addCard(Zone.BATTLEFIELD, playerA, "Memnite"); + addCard(Zone.BATTLEFIELD, playerA, "Abbey Griffin"); + addCard(Zone.HAND, playerA, "Doom Blade"); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); + + setModeChoice(playerA, "1"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Doom Blade", kairi); + addTarget(playerA, "Abbey Griffin^Memnite"); // returning both Griffin & Memnite + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertHandCount(playerA, "Abbey Griffin", 1); + assertHandCount(playerA, "Memnite", 1); + } + + @Test + public void test_Cant_Return_MV7() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, kairi); + addCard(Zone.BATTLEFIELD, playerA, "Axebane Stag"); + addCard(Zone.HAND, playerA, "Doom Blade"); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); + + setModeChoice(playerA, "1"); // choose mode #1 + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Doom Blade", kairi); + addTarget(playerA, "Axebane Stag"); // returning Axebane Stag is an invalid target + + setStopAt(1, PhaseStep.END_TURN); + String foundError = ""; + try { + execute(); + } catch (AssertionError e) { + foundError = e.getMessage(); + } + Assert.assertEquals("PlayerA - Targets list was setup by addTarget with [Axebane Stag], but not used", foundError.split("\n")[0]); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/otc/CataclysmicProspectingTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/otc/CataclysmicProspectingTest.java new file mode 100644 index 00000000000..79530c4d40e --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/otc/CataclysmicProspectingTest.java @@ -0,0 +1,58 @@ +package org.mage.test.cards.single.otc; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class CataclysmicProspectingTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.c.CataclysmicProspecting Cataclysmic Prospecting} {X}{R}{R} + * Sorcery + * Cataclysmic Prospecting deals X damage to each creature. For each mana from a Desert spent to cast this spell, create a tapped Treasure token. + */ + private static final String prospecting = "Cataclysmic Prospecting"; + + @Test + public void test_Two_Desert() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerB, "Indomitable Ancients"); // 2/10 + addCard(Zone.BATTLEFIELD, playerA, "Hostile Desert", 2); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); + addCard(Zone.HAND, playerA, prospecting); + + setChoice(playerA, "X=5"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, prospecting); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, prospecting, 1); + assertDamageReceived(playerB, "Indomitable Ancients", 5); + assertPermanentCount(playerA, "Treasure Token", 2); + } + + @Test + public void test_Zero_Desert() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerB, "Indomitable Ancients"); // 2/10 + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 7); + addCard(Zone.HAND, playerA, prospecting); + + setChoice(playerA, "X=5"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, prospecting); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, prospecting, 1); + assertDamageReceived(playerB, "Indomitable Ancients", 5); + assertPermanentCount(playerA, "Treasure Token", 0); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/otc/DuneChanterTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/otc/DuneChanterTest.java new file mode 100644 index 00000000000..0dc4b879caa --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/otc/DuneChanterTest.java @@ -0,0 +1,108 @@ +package org.mage.test.cards.single.otc; + +import mage.constants.PhaseStep; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.game.Game; +import mage.players.Player; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class DuneChanterTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.d.DuneChanter Dune Chanter} {2}{G} + * Creature — Plant Druid + * Reach + * Lands you control and land cards you own that aren’t on the battlefield are Deserts in addition to their other types. + * Lands you control have “{T}: Add one mana of any color.” + * {T}: Mill two cards. You gain 1 life for each land card milled this way. + * 2/3 + */ + private static final String chanter = "Dune Chanter"; + + private static void checkBattlefield(String info, Player player, Game game, int count) { + int amount = game + .getBattlefield() + .getAllActivePermanents(player.getId()) + .stream() + .filter(p -> p.getSubtype(game).contains(SubType.DESERT)) + .mapToInt(k -> 1) + .sum(); + Assert.assertEquals(info, count, amount); + } + + private static void checkTopLibrary(String info, Player player, Game game, boolean check) { + boolean hasDesert = player.getLibrary().getFromTop(game).getSubtype(game).contains(SubType.DESERT); + Assert.assertEquals(info, check, hasDesert); + } + + private static void checkHand(String info, Player player, Game game, int count) { + int amount = player + .getHand() + .getCards(game) + .stream() + .filter(c -> c.getSubtype(game).contains(SubType.DESERT)) + .mapToInt(k -> 1) + .sum(); + Assert.assertEquals(info, count, amount); + } + + private static void checkGraveyard(String info, Player player, Game game, int count) { + int amount = player + .getGraveyard() + .getCards(game) + .stream() + .filter(c -> c.getSubtype(game).contains(SubType.DESERT)) + .mapToInt(k -> 1) + .sum(); + Assert.assertEquals(info, count, amount); + } + + private static void checkExile(String info, Player player, Game game, int count) { + int amount = game + .getExile() + .getAllCards(game, player.getId()) + .stream() + .filter(c -> c.getSubtype(game).contains(SubType.DESERT)) + .mapToInt(k -> 1) + .sum(); + Assert.assertEquals(info, count, amount); + } + + @Test + public void test_Deserts_All_Zones() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.HAND, playerA, chanter); + addCard(Zone.BATTLEFIELD, playerA, "Bayou", 3); + addCard(Zone.HAND, playerA, "Tropical Island"); + addCard(Zone.GRAVEYARD, playerA, "Volcanic Island"); + addCard(Zone.EXILED, playerA, "Scrubland"); + addCard(Zone.LIBRARY, playerA, "Badlands"); + + runCode("Check battlefield before", 1, PhaseStep.UPKEEP, playerA, (i, p, g) -> DuneChanterTest.checkBattlefield(i, p, g, 0)); + runCode("Check hand before ", 1, PhaseStep.UPKEEP, playerA, (i, p, g) -> DuneChanterTest.checkHand(i, p, g, 0)); + runCode("Check library before ", 1, PhaseStep.UPKEEP, playerA, (i, p, g) -> DuneChanterTest.checkTopLibrary(i, p, g, false)); + runCode("Check graveyard before ", 1, PhaseStep.UPKEEP, playerA, (i, p, g) -> DuneChanterTest.checkGraveyard(i, p, g, 0)); + runCode("Check exile before ", 1, PhaseStep.UPKEEP, playerA, (i, p, g) -> DuneChanterTest.checkExile(i, p, g, 0)); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, chanter, true); + + runCode("Check battlefield after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (i, p, g) -> DuneChanterTest.checkBattlefield(i, p, g, 3)); + runCode("Check hand after ", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (i, p, g) -> DuneChanterTest.checkHand(i, p, g, 1)); + runCode("Check library after ", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (i, p, g) -> DuneChanterTest.checkTopLibrary(i, p, g, true)); + runCode("Check graveyard after ", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (i, p, g) -> DuneChanterTest.checkGraveyard(i, p, g, 1)); + runCode("Check exile after ", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (i, p, g) -> DuneChanterTest.checkExile(i, p, g, 1)); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, chanter, 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/otc/SmirkingSpelljackerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/otc/SmirkingSpelljackerTest.java new file mode 100644 index 00000000000..cc9de72be79 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/otc/SmirkingSpelljackerTest.java @@ -0,0 +1,51 @@ +package org.mage.test.cards.single.otc; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class SmirkingSpelljackerTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.s.SmirkingSpelljacker Smirking Spelljacker} {4}{U} + * Creature — Djinn Wizard Rogue + * Flash + * Flying + * When Smirking Spelljacker enters the battlefield, exile target spell an opponent controls. + * Whenever Smirking Spelljacker attacks, if a card is exiled with it, you may cast the exiled card without paying its mana cost. + * 3/3 + */ + private static final String spelljacker = "Smirking Spelljacker"; + + @Test + public void test_Simple() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerB, spelljacker); + addCard(Zone.HAND, playerA, "Lightning Bolt", 1); + addCard(Zone.BATTLEFIELD, playerB, "Island", 5); + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, spelljacker); + addTarget(playerB, "Lightning Bolt"); + + checkExileCount("Bolt in exile", 1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Lightning Bolt", 1); + + attack(2, playerB, spelljacker, playerA); + setChoice(playerB, "Lightning Bolt"); // choosing bolt + setChoice(playerB, true); // yes to cast bolt + addTarget(playerB, playerA); // target for bolt + + setStopAt(2, PhaseStep.END_COMBAT); + execute(); + + assertLife(playerA, 20 - 3 - 3); // 3 from bolt, 3 from spelljacker + assertPermanentCount(playerB, spelljacker, 1); + assertGraveyardCount(playerA, "Lightning Bolt", 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/otj/AssimilationAegisTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/otj/AssimilationAegisTest.java new file mode 100644 index 00000000000..02b0b202c4c --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/otj/AssimilationAegisTest.java @@ -0,0 +1,67 @@ +package org.mage.test.cards.single.otj; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class AssimilationAegisTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.a.AssimilationAegis Assimilation Aegis} {1}{W}{U} + * Artifact — Equipment + * When Assimilation Aegis enters the battlefield, exile up to one target creature until Assimilation Aegis leaves the battlefield. + * Whenever Assimilation Aegis becomes attached to a creature, for as long as Assimilation Aegis remains attached to it, that creature becomes a copy of a creature card exiled with Assimilation Aegis. + * Equip {2} + */ + private static final String aegis = "Assimilation Aegis"; + + @Test + public void test_Equip_Equip_Disenchant() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Tundra", 9); + addCard(Zone.HAND, playerA, aegis); + addCard(Zone.HAND, playerA, "Disenchant"); // to test when Aegis leaves the battlefield + addCard(Zone.BATTLEFIELD, playerA, "Elite Vanguard"); + addCard(Zone.BATTLEFIELD, playerA, "Goblin Piker"); + addCard(Zone.BATTLEFIELD, playerB, "Grizzly Bears"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, aegis); + addTarget(playerA, "Grizzly Bears"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + checkExileCount("After etb: Bears in exile ", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grizzly Bears", 1); + checkPermanentCount("After etb: No Bears in play ", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grizzly Bears", 0); + checkPermanentCount("After etb: Piker in play ", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Goblin Piker", 1); + checkPermanentCount("After etb: Vanguard in play ", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Elite Vanguard", 1); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Equip", "Goblin Piker"); + setChoice(playerA, "Grizzly Bears"); // choose what card to copy (there could be multiple) + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + checkPermanentCount("After equip Piker: Bears (copy) in play ", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grizzly Bears", 1); + checkPermanentCount("After equip Piker: No Piker in play ", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Goblin Piker", 0); + checkPermanentCount("After equip Piker: Vanguard in play ", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Elite Vanguard", 1); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Equip", "Elite Vanguard"); + setChoice(playerA, "Grizzly Bears"); // choose what card to copy (there could be multiple) + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + checkPermanentCount("After equip Vanguard: Bears (copy) in play ", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grizzly Bears", 1); + checkPermanentCount("After equip Vanguard: Piker in play ", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Goblin Piker", 1); + checkPermanentCount("After equip Vanguard: No Vanguard in play ", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Elite Vanguard", 0); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Disenchant", aegis); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + checkPermanentCount("After Disenchant: Bears in play (on playerB side)", 1, PhaseStep.PRECOMBAT_MAIN, playerA, playerB, "Grizzly Bears", 1); + checkPermanentCount("After Disenchant: Piker in play ", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Goblin Piker", 1); + checkPermanentCount("After Disenchant: Vanguard in play ", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Elite Vanguard", 1); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, aegis, 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/otj/DocAurlockGrizzledGeniusTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/otj/DocAurlockGrizzledGeniusTest.java new file mode 100644 index 00000000000..3e4b56abb75 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/otj/DocAurlockGrizzledGeniusTest.java @@ -0,0 +1,51 @@ +package org.mage.test.cards.single.otj; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class DocAurlockGrizzledGeniusTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.d.DocAurlockGrizzledGenius Doc Aurlock, Grizzled Genius} {G}{U} + * Legendary Creature — Bear Druid + * Spells you cast from your graveyard or from exile cost {2} less to cast. + * Plotting cards from your hand costs {2} less. + * 2/3 + */ + private static final String doc = "Doc Aurlock, Grizzled Genius"; + + /** + * {@link mage.cards.d.DjinnOfFoolsFall Djinn of Fool's Fall} {4}{U} + * Creature — Djinn + * Flying + * Plot {3}{U} + * 4/3 + */ + private static final String djinn = "Djinn of Fool's Fall"; + + @Test + public void TestPlottingCostReduction() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, doc); + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + addCard(Zone.HAND, playerA, djinn); + + checkPlayableAbility("plot can't be used during upkeep", 1, PhaseStep.UPKEEP, playerA, "Plot", false); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Plot"); + checkExileCount("plot is in exile", 1, PhaseStep.PRECOMBAT_MAIN, playerA, djinn, 1); + + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, djinn + " using Plot"); + + setStopAt(3, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, djinn, 1); + assertTappedCount("Island", true, 0); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/otj/ErthaJoFrontierMentorTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/otj/ErthaJoFrontierMentorTest.java new file mode 100644 index 00000000000..4769b5fac46 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/otj/ErthaJoFrontierMentorTest.java @@ -0,0 +1,76 @@ +package org.mage.test.cards.single.otj; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class ErthaJoFrontierMentorTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.e.ErthaJoFrontierMentor Ertha Jo, Frontier Mentor} {2}{R}{W} + * Legendary Creature — Kor Advisor + * When Ertha Jo, Frontier Mentor enters the battlefield, create a 1/1 red Mercenary creature token with “{T}: Target creature you control gets +1/+0 until end of turn. Activate only as a sorcery.” + * Whenever you activate an ability that targets a creature or player, copy that ability. You may choose new targets for the copy. + * 2/4 + */ + private static final String ertha = "Ertha Jo, Frontier Mentor"; + + @Test + public void Test_TargetPlayer() { + setStrictChooseMode(true); + + // Sacrifice Bile Urchin: Target player loses 1 life. + addCard(Zone.BATTLEFIELD, playerA, "Bile Urchin", 1); + addCard(Zone.BATTLEFIELD, playerA, ertha, 1); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sacrifice", playerB); + setChoice(playerA, true); // choose to change the targets for the copy + addTarget(playerA, playerA); // have the copy target playerA + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertLife(playerA, 19); + assertLife(playerB, 19); + } + + @Test + public void Test_TargetCreature() { + setStrictChooseMode(true); + + // {T}: Target creature gets +1/+1 until end of turn. + addCard(Zone.BATTLEFIELD, playerA, "Wyluli Wolf", 1); + addCard(Zone.BATTLEFIELD, playerA, ertha, 1); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}", ertha); + setChoice(playerA, false); // choose not to change the targets for the copy + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPowerToughness(playerA, ertha, 2 + 2, 4 + 2); + } + + @Test + public void Test_TargetLand_NoCopy() { + setStrictChooseMode(true); + + // {2}{R}, {T}: Destroy target nonbasic land. + addCard(Zone.BATTLEFIELD, playerA, "Dwarven Miner", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + addCard(Zone.BATTLEFIELD, playerB, "Plateau", 1); + addCard(Zone.BATTLEFIELD, playerB, "Tropical Island", 1); + addCard(Zone.BATTLEFIELD, playerA, ertha, 1); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}{R}", "Plateau"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerB, "Plateau", 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/otj/FblthpLostOnTheRangeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/otj/FblthpLostOnTheRangeTest.java new file mode 100644 index 00000000000..9e29a657159 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/otj/FblthpLostOnTheRangeTest.java @@ -0,0 +1,120 @@ +package org.mage.test.cards.single.otj; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class FblthpLostOnTheRangeTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.f.FblthpLostOnTheRange Fblthp, Lost on the Range} {1}{U}{U} + * Legendary Creature — Homunculus + * Ward {2} + * You may look at the top card of your library any time. + * The top card of your library has plot. The plot cost is equal to its mana cost. + * You may plot nonland cards from the top of your library. + * 1/1 + */ + private static final String fblthp = "Fblthp, Lost on the Range"; + + @Test + public void Test_Plot_FromTop_LightningBolt() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.BATTLEFIELD, playerA, fblthp); + addCard(Zone.LIBRARY, playerA, "Lightning Bolt"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + + assertHandCount(playerA, 0); // no card in hand, Bolt is on top. + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Plot"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertExileCount(playerA, "Lightning Bolt", 1); + assertTappedCount("Mountain", true, 1); // cost {R} to plot + } + + @Test + public void Test_Plot_FromTop_RegularPlot() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.BATTLEFIELD, playerA, fblthp); + addCard(Zone.LIBRARY, playerA, "Beastbond Outcaster"); // {2}{G}, plot {1}{G} + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + + assertHandCount(playerA, 0); // no card in hand, Outcaster is on top. + checkPlayableAbility("regular Plot {1}{G}", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Plot {1}{G}", true); + checkPlayableAbility("no mana for added Plot {2}{G}", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Plot {2}{G}", false); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Plot {1}{G}"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertExileCount(playerA, "Beastbond Outcaster", 1); + assertTappedCount("Forest", true, 2); + } + + @Test + public void Test_Plot_FromTop_AddedPlot() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.BATTLEFIELD, playerA, fblthp); + addCard(Zone.LIBRARY, playerA, "Beastbond Outcaster"); // {2}{G}, plot {1}{G} + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + + assertHandCount(playerA, 0); // no card in hand, Outcaster is on top. + checkPlayableAbility("regular Plot {1}{G}", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Plot {1}{G}", true); + checkPlayableAbility("added Plot {2}{G}", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Plot {2}{G}", true); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Plot {2}{G}"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertExileCount(playerA, "Beastbond Outcaster", 1); + assertTappedCount("Forest", true, 3); + } + + @Test + public void Test_Plot_FromTop_Adventure() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.BATTLEFIELD, playerA, fblthp); + addCard(Zone.LIBRARY, playerA, "Bonecrusher Giant"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Plot {2}{R}"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertExileCount(playerA, "Bonecrusher Giant", 1); + assertTappedCount("Mountain", true, 3); + } + + @Test + public void Test_Plot_FromTop_Split() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.BATTLEFIELD, playerA, fblthp); + addCard(Zone.LIBRARY, playerA, "Life // Death"); // split {G} / {1}{B} + addCard(Zone.BATTLEFIELD, playerA, "Bayou", 3); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Plot"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertExileCount(playerA, "Life // Death", 1); + assertTappedCount("Bayou", true, 3); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/otj/FreestriderCommandoTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/otj/FreestriderCommandoTest.java new file mode 100644 index 00000000000..f1fe11f3735 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/otj/FreestriderCommandoTest.java @@ -0,0 +1,127 @@ +package org.mage.test.cards.single.otj; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class FreestriderCommandoTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.f.FreestriderCommando Freestrider Commando} {2}{G} + * Creature — Centaur Mercenary + * Freestrider Commando enters the battlefield with two +1/+1 counters on it if it wasn’t cast or no mana was spent to cast it. + * Plot {3}{G} (You may pay {3}{G} and exile this card from your hand. Cast it as a sorcery on a later turn without paying its mana cost. Plot only as a sorcery.) + * 3/3 + */ + private static final String commando = "Freestrider Commando"; + + @Test + public void test_RegularCast() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + addCard(Zone.HAND, playerA, commando); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, commando); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + // No +1/+1 as cast with mana + assertPowerToughness(playerA, commando, 3, 3); + } + + @Test + public void test_PlotCast() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Forest", 4); + addCard(Zone.HAND, playerA, commando); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Plot"); + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, commando + " using Plot"); + + setStopAt(3, PhaseStep.BEGIN_COMBAT); + execute(); + + // 2 +1/+1 as cast free with plot + assertPowerToughness(playerA, commando, 3 + 2, 3 + 2); + } + + @Test + public void test_OmniscienceCast() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Omniscience"); + addCard(Zone.HAND, playerA, commando); + + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, commando); + setChoice(playerA, true); // Omniscience asks for confirmation to cast to avoid missclick? + + setStopAt(3, PhaseStep.BEGIN_COMBAT); + execute(); + + // 2 +1/+1 as cast free with Omniscience + assertPowerToughness(playerA, commando, 3 + 2, 3 + 2); + } + + @Test + public void test_PlotCast_WithTax() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Forest", 4); + addCard(Zone.HAND, playerA, commando); + addCard(Zone.BATTLEFIELD, playerA, "Sphere of Resistance"); // Spells cost {1} more to cast + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Plot"); + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, commando + " using Plot"); + + setStopAt(3, PhaseStep.BEGIN_COMBAT); + execute(); + + // no +1/+1 as cast free with plot, but tax make mana being paid + assertPowerToughness(playerA, commando, 3, 3); + } + + @Test + public void test_Blink() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Savannah", 4); + addCard(Zone.HAND, playerA, commando); + addCard(Zone.HAND, playerA, "Ephemerate"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, commando); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Ephemerate", "Freestrider Commando"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + // 2 +1/+1 as cast free with plot, as re-enter without being cast + assertPowerToughness(playerA, commando, 3 + 2, 3 + 2); + } + + @Test + public void test_DoubleMajor() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Tropical Island", 6); + addCard(Zone.HAND, playerA, commando); + addCard(Zone.HAND, playerA, "Double Major"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, commando); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Double Major", "Freestrider Commando"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, 2); // resolve Double Major + the copy + checkPT("double major copy enters as a 5/5", 1, PhaseStep.PRECOMBAT_MAIN, playerA, commando, 3 + 2, 3 + 2); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, commando, 2); // real + copy + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/otj/KambalProfiteeringMayorTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/otj/KambalProfiteeringMayorTest.java new file mode 100644 index 00000000000..5866e8a215f --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/otj/KambalProfiteeringMayorTest.java @@ -0,0 +1,89 @@ +package org.mage.test.cards.single.otj; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class KambalProfiteeringMayorTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.k.KambalProfiteeringMayor Kambal, Profiteering Mayor} {1}{W}{B} + * Legendary Creature — Human Advisor + * Whenever one or more tokens enter the battlefield under your opponents’ control, for each of them, create a tapped token that’s a copy of it. This ability triggers only once each turn. + * Whenever one or more tokens enter the battlefield under your control, each opponent loses 1 life and you gain 1 life. + * 2/4 + */ + private static final String kambal = "Kambal, Profiteering Mayor"; + + @Test + public void Test_KrenkosCommand() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerB, kambal); + // Create two 1/1 red Goblin creature tokens. + addCard(Zone.HAND, playerA, "Krenko's Command"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Krenko's Command"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertLife(playerA, 20 - 1); // Second Trigger happened + assertLife(playerB, 20 + 1); // Second Trigger happened + assertPermanentCount(playerA, "Goblin Token", 2); + assertPermanentCount(playerB, "Goblin Token", 2); + } + + @Test + public void Test_BestialMenace() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerB, kambal); + // Create a 1/1 green Snake creature token, a 2/2 green Wolf creature token, and a 3/3 green Elephant creature token. + addCard(Zone.HAND, playerA, "Bestial Menace"); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 5); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bestial Menace"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertLife(playerA, 20 - 1); // Second Trigger happened + assertLife(playerB, 20 + 1); // Second Trigger happened + assertPermanentCount(playerA, "Snake Token", 1); + assertPermanentCount(playerA, "Wolf Token", 1); + assertPermanentCount(playerA, "Elephant Token", 1); + assertPermanentCount(playerB, "Snake Token", 1); + assertPermanentCount(playerB, "Wolf Token", 0); // TODO: this is a bug, should be 1, see #10811 + assertPermanentCount(playerB, "Elephant Token", 0); // TODO: this is a bug, should be 1, see #10811 + } + + @Test + public void Test_Chatterfang() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerB, kambal); + // Create two 1/1 red Goblin creature tokens. + addCard(Zone.HAND, playerA, "Krenko's Command"); + // If one or more tokens would be created under your control, those tokens plus that many 1/1 green Squirrel creature tokens are created instead. + addCard(Zone.BATTLEFIELD, playerA, "Chatterfang, Squirrel General"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Krenko's Command"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertLife(playerA, 20 - 1); // Second Trigger happened + assertLife(playerB, 20 + 1); // Second Trigger happened + assertPermanentCount(playerA, "Goblin Token", 2); + assertPermanentCount(playerA, "Squirrel Token", 2); + assertPermanentCount(playerB, "Goblin Token", 2); + assertPermanentCount(playerB, "Squirrel Token", 2); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/otj/ObekaSplitterOfSecondsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/otj/ObekaSplitterOfSecondsTest.java new file mode 100644 index 00000000000..8179c506464 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/otj/ObekaSplitterOfSecondsTest.java @@ -0,0 +1,38 @@ +package org.mage.test.cards.single.otj; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class ObekaSplitterOfSecondsTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.o.ObekaSplitterOfSeconds Obeka, Splitter of Seconds} {1}{U}{B}{R} + * Legendary Creature — Ogre Warlock + * Menace + * Whenever Obeka, Splitter of Seconds deals combat damage to a player, you get that many additional upkeep steps after this phase. + * 2/5 + */ + private static final String obeka = "Obeka, Splitter of Seconds"; + + @Test + public void test_ExtraUpkeep() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, obeka); + // At the beginning of your upkeep, you gain 1 life. + addCard(Zone.BATTLEFIELD, playerA, "Fountain of Renewal"); + + attack(1, playerA, obeka, playerB); + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertLife(playerA, 20 + 1 + 2); // 1 regular upkeep + 2 obeka ones + assertTapped(obeka, true); // checks that no extra untap happened + assertHandCount(playerA, 0); // checks that no draw step happened + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/otj/RikuOfManyPathsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/otj/RikuOfManyPathsTest.java new file mode 100644 index 00000000000..023b6db458d --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/otj/RikuOfManyPathsTest.java @@ -0,0 +1,46 @@ +package org.mage.test.cards.single.otj; + +import mage.abilities.keyword.TrampleAbility; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author TheElk801 + */ +public class RikuOfManyPathsTest extends CardTestPlayerBase { + + private static final String riku = "Riku of Many Paths"; + private static final String takedown = "Artful Takedown"; + private static final String bear = "Grizzly Bears"; + private static final String lion = "Silvercoat Lion"; + + @Test + public void testModeChoice() { + addCard(Zone.BATTLEFIELD, playerA, "Underground Sea", 4); + addCard(Zone.BATTLEFIELD, playerA, riku); + addCard(Zone.BATTLEFIELD, playerA, bear); + addCard(Zone.BATTLEFIELD, playerA, lion); + addCard(Zone.HAND, playerA, takedown); + + setModeChoice(playerA, "1"); + setModeChoice(playerA, "2"); + addTarget(playerA, lion); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, takedown, bear); + + setModeChoice(playerA, "2"); + setModeChoice(playerA, "3"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertTapped(bear, true); + assertGraveyardCount(playerA, lion, 1); + assertCounterCount(playerA, riku, CounterType.P1P1, 1); + assertAbility(playerA, riku, TrampleAbility.getInstance(), true); + assertPermanentCount(playerA, "Bird Token", 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/otj/SatoruTheInfiltratorTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/otj/SatoruTheInfiltratorTest.java new file mode 100644 index 00000000000..6e6644d8d5e --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/otj/SatoruTheInfiltratorTest.java @@ -0,0 +1,268 @@ +package org.mage.test.cards.single.otj; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class SatoruTheInfiltratorTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.s.SatoruTheInfiltrator Satoru, the Infiltrator} {U}{B} + * Legendary Creature — Human Ninja Rogue + * Menace + * Whenever Satoru, the Infiltrator and/or one or more other nontoken creatures enter the battlefield under your control, if none of them were cast or no mana was spent to cast them, draw a card. + */ + private static final String satoru = "Satoru, the Infiltrator"; + + /** + * {@link mage.cards.f.FreestriderCommando Freestrider Commando} {2}{G} + * Creature — Centaur Mercenary + * Freestrider Commando enters the battlefield with two +1/+1 counters on it if it wasn’t cast or no mana was spent to cast it. + * Plot {3}{G} (You may pay {3}{G} and exile this card from your hand. Cast it as a sorcery on a later turn without paying its mana cost. Plot only as a sorcery.) + * 3/3 + */ + private static final String commando = "Freestrider Commando"; + + @Test + public void test_RegularCast_Other() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, satoru); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + addCard(Zone.HAND, playerA, commando); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, commando); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, commando, 1); + assertHandCount(playerA, 0); // no card draw. + } + + @Test + public void test_RegularCast_Other_Memnite() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, satoru); + addCard(Zone.HAND, playerA, "Memnite"); // Is free! + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Memnite"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Memnite", 1); + assertHandCount(playerA, 1); + } + + @Test + public void test_RegularCast_Satoru() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Underground Sea", 2); + addCard(Zone.HAND, playerA, satoru); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, satoru); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, satoru, 1); + assertHandCount(playerA, 0); // no card draw. + } + + @Test + public void test_PlotCast() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, satoru); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 4); + addCard(Zone.HAND, playerA, commando); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Plot"); + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, commando + " using Plot"); + + setStopAt(3, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, commando, 1); + assertHandCount(playerA, 1 + 1); // Drawn 1 from draw step + 1 from Satoru + } + + @Test + public void test_Omniscience_CastOther() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, satoru); + addCard(Zone.BATTLEFIELD, playerA, "Omniscience"); + addCard(Zone.HAND, playerA, commando); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, commando); + setChoice(playerA, true); // Omniscience asks for confirmation to cast to avoid missclick? + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, commando, 1); + assertHandCount(playerA, 1); // Drawn 1 from Satoru + } + + @Test + public void test_Omniscience_CastSatoru() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Omniscience"); + addCard(Zone.HAND, playerA, satoru); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, satoru); + setChoice(playerA, true); // Omniscience asks for confirmation to cast to avoid missclick? + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, satoru, 1); + assertHandCount(playerA, 1); // Drawn 1 from Satoru + } + + @Test + public void test_PlotCast_WithTax() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, satoru); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 4); + addCard(Zone.HAND, playerA, commando); + addCard(Zone.BATTLEFIELD, playerA, "Sphere of Resistance"); // Spells cost {1} more to cast + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Plot"); + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, commando + " using Plot"); + + setStopAt(3, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, commando, 1); + assertHandCount(playerA, 1); // Drawn 1 from draw step, 0 from Satoru + } + + @Test + public void test_Blink() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, satoru); + addCard(Zone.BATTLEFIELD, playerA, "Savannah", 4); + addCard(Zone.HAND, playerA, commando); + addCard(Zone.HAND, playerA, "Ephemerate"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, commando); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Ephemerate", commando); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, commando, 1); + assertHandCount(playerA, 1); // Drawn 1 from Satoru + } + + @Test + public void test_MultipleOtherEnter() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, satoru); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 6); + addCard(Zone.GRAVEYARD, playerA, commando, 3); + addCard(Zone.HAND, playerA, "Storm of Souls"); // Return all creature cards from your graveyard to the battlefield. Each of them is a 1/1 Spirit with flying in addition to its other types. Exile Storm of Souls. + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Storm of Souls"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, commando, 3); + assertHandCount(playerA, 1); // Drawn 1 from Satoru + } + + @Test + public void test_Saturo_AndAnother_Enter() { + setStrictChooseMode(true); + + addCard(Zone.GRAVEYARD, playerA, satoru); + addCard(Zone.GRAVEYARD, playerA, commando, 3); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 5); + // Each player exiles all creature cards from their graveyard, then sacrifices all creatures they control, + // then puts all cards they exiled this way onto the battlefield. + addCard(Zone.HAND, playerA, "Living Death"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Living Death"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, commando, 3); + assertPermanentCount(playerA, satoru, 1); + assertHandCount(playerA, 1); // Drawn 1 from Satoru + } + + @Test + public void test_CopyOnStack() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, satoru); + addCard(Zone.HAND, playerA, commando); + addCard(Zone.BATTLEFIELD, playerA, "Tropical Island", 5); + // Copy target creature spell you control, except it isn’t legendary if the spell is legendary + addCard(Zone.HAND, playerA, "Double Major"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, commando); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Double Major", commando); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, commando, 2); + assertPermanentCount(playerA, satoru, 1); + assertHandCount(playerA, 0); // Drawn 0 from Satoru, as token does not count. + } + + @Test + public void test_Tokens() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, satoru); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.HAND, playerA, "Krenko's Command"); // Create two 1/1 red Goblin creature tokens. + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Krenko's Command"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Goblin Token", 2); + assertPermanentCount(playerA, satoru, 1); + assertHandCount(playerA, 0); // Drawn 0 from Satoru, as token does not count. + } + + @Test + public void test_CopySatoruOnStack() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, satoru); + addCard(Zone.BATTLEFIELD, playerA, "Tropical Island", 3); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); + // Copy target creature spell you control, except it isn’t legendary if the spell is legendary + addCard(Zone.HAND, playerA, "Double Major"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, satoru); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Double Major", satoru); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, satoru, 2); + assertHandCount(playerA, 1); // While Satoru does not trigger on other tokens, a copy of Satoru on the stack will trigger its own etb. + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/otj/SlickSequenceTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/otj/SlickSequenceTest.java new file mode 100644 index 00000000000..6862e141ffa --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/otj/SlickSequenceTest.java @@ -0,0 +1,123 @@ +package org.mage.test.cards.single.otj; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class SlickSequenceTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.s.SlickSequence Slick Sequence} {U}{R} + * Instant + * Slick Sequence deals 2 damage to any target. If you’ve cast another spell this turn, draw a card. + */ + private static final String sequence = "Slick Sequence"; + + @Test + public void testOne() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 2); + addCard(Zone.HAND, playerA, sequence); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, sequence, playerB); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + setStrictChooseMode(true); + execute(); + + assertGraveyardCount(playerA, sequence, 1); + assertLife(playerB, 20 - 2); + assertHandCount(playerA, 0); + } + + @Test + public void testOneResolveThenAnother() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 4); + addCard(Zone.HAND, playerA, sequence, 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, sequence, playerB); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, sequence, playerB); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + setStrictChooseMode(true); + execute(); + + assertGraveyardCount(playerA, sequence, 2); + assertLife(playerB, 20 - 2 * 2); + assertHandCount(playerA, 1); + } + + @Test + public void testOneThenAnotherInResponse() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 4); + addCard(Zone.HAND, playerA, sequence, 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, sequence, playerB); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, sequence, playerB); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + setStrictChooseMode(true); + execute(); + + assertGraveyardCount(playerA, sequence, 2); + assertLife(playerB, 20 - 2 * 2); + assertHandCount(playerA, 1 * 2); + } + + @Test + public void testOneRemandedThenRecast() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 4); + addCard(Zone.HAND, playerA, sequence, 1); + addCard(Zone.BATTLEFIELD, playerB, "Island", 2); + addCard(Zone.HAND, playerB, "Remand", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, sequence, playerB); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Remand", sequence); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, sequence, playerB); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + setStrictChooseMode(true); + execute(); + + assertGraveyardCount(playerA, sequence, 1); + assertGraveyardCount(playerB, "Remand", 1); + assertLife(playerB, 20 - 2); + assertHandCount(playerA, 1); + } + + @Test + public void testOtherPlayerSpellsNotCounting() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 2); + addCard(Zone.HAND, playerA, sequence, 1); + addCard(Zone.BATTLEFIELD, playerB, "Mountain", 1); + addCard(Zone.HAND, playerB, "Lightning Bolt", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", playerB); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, sequence, playerB); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + setStrictChooseMode(true); + execute(); + + assertGraveyardCount(playerA, sequence, 1); + assertGraveyardCount(playerB, "Lightning Bolt", 1); + assertLife(playerB, 20 - 2 - 3); + assertHandCount(playerA, 0); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/otj/TinybonesThePickpocketTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/otj/TinybonesThePickpocketTest.java new file mode 100644 index 00000000000..8358fb6a770 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/otj/TinybonesThePickpocketTest.java @@ -0,0 +1,59 @@ +package org.mage.test.cards.single.otj; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class TinybonesThePickpocketTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.t.TinybonesThePickpocket Tinybones, the Pickpocket} {B} + * Legendary Creature — Skeleton Rogue + * Deathtouch + * Whenever Tinybones, the Pickpocket deals combat damage to a player, you may cast target nonland permanent card from that player's graveyard, and mana of any type can be spent to cast that spell. + * 1/1 + */ + private static final String tinybones = "Tinybones, the Pickpocket"; + + @Test + public void test_CastPermanent_WithOtherType() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, tinybones); + addCard(Zone.GRAVEYARD, playerB, "Raging Goblin"); + addCard(Zone.BATTLEFIELD, playerA, "Swamp"); + + attack(1, playerA, tinybones, playerB); + addTarget(playerA, "Raging Goblin"); // target card for the trigger + setChoice(playerA, true); // yes to "you may cast" + + setStopAt(1, PhaseStep.END_COMBAT); + execute(); + + assertPermanentCount(playerA, "Raging Goblin", 1); + assertTapped("Swamp", true); // It did cost 1 mana + } + + @Test + public void test_NoToCast() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, tinybones); + addCard(Zone.GRAVEYARD, playerB, "Raging Goblin"); + addCard(Zone.BATTLEFIELD, playerA, "Swamp"); + + attack(1, playerA, tinybones, playerB); + addTarget(playerA, "Raging Goblin"); // target card for the trigger + setChoice(playerA, false); // no to "you may cast" + + setStopAt(1, PhaseStep.END_COMBAT); + execute(); + + assertGraveyardCount(playerB, "Raging Goblin", 1); // card did not move. + assertTapped("Swamp", false); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/otj/VraskaTheSilencerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/otj/VraskaTheSilencerTest.java new file mode 100644 index 00000000000..ec27b0c6060 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/otj/VraskaTheSilencerTest.java @@ -0,0 +1,52 @@ +package org.mage.test.cards.single.otj; + +import mage.constants.CardType; +import mage.constants.PhaseStep; +import mage.constants.SubType; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class VraskaTheSilencerTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.v.VraskaTheSilencer Vraska, the Silencer} {1}{B}{G} + * Legendary Creature — Gorgon Assassin + * Deathtouch + * Whenever a nontoken creature an opponent controls dies, you may pay {1}. If you do, return that card to the battlefield tapped under your control. It’s a Treasure artifact with “{T}, Sacrifice this artifact: Add one mana of any color,” and it loses all other card types. + * 3/3 + */ + private static final String vraska = "Vraska, the Silencer"; + + @Test + public void test_CorrectTypes() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, vraska); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); + addCard(Zone.HAND, playerA, "Doom Blade"); + addCard(Zone.BATTLEFIELD, playerB, "Red Herring"); // Creature Artifact -- Fish Clue + // Whenever another creature enters the battlefield, you gain 1 life. + // Should not trigger. Just to check the continuous effect is done before the permanent entering modified. + addCard(Zone.BATTLEFIELD, playerA, "Soul Warden"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Doom Blade", "Red Herring"); + setChoice(playerA, true); // yes to "You may pay" + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + setStrictChooseMode(true); + execute(); + + assertPermanentCount(playerA, "Red Herring", 1); + assertTapped("Red Herring", true); + assertType("Red Herring", CardType.CREATURE, false); + assertType("Red Herring", CardType.ARTIFACT, true); + assertNotSubtype("Red Herring", SubType.FISH); + assertSubtype("Red Herring", SubType.CLUE); + assertSubtype("Red Herring", SubType.TREASURE); + assertLife(playerA, 20); // no Soul Warden trigger + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/shm/MemoryPlunderTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/shm/MemoryPlunderTest.java new file mode 100644 index 00000000000..798fe998e22 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/shm/MemoryPlunderTest.java @@ -0,0 +1,57 @@ +package org.mage.test.cards.single.shm; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class MemoryPlunderTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.m.MemoryPlunder Memory Plunder} {U/B}{U/B}{U/B}{U/B} + * Instant + * You may cast target instant or sorcery card from an opponent’s graveyard without paying its mana cost. + */ + private static final String plunder = "Memory Plunder"; + + @Test + public void test_Divination_Cast() { + setStrictChooseMode(true); + + addCard(Zone.GRAVEYARD, playerB, "Divination"); + addCard(Zone.HAND, playerA, plunder); + addCard(Zone.BATTLEFIELD, playerA, "Island", 4); + + castSpell(1, PhaseStep.UPKEEP, playerA, plunder, "Divination"); + setChoice(playerA, true); // yes to cast + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertGraveyardCount(playerA, plunder, 1); + assertGraveyardCount(playerB, "Divination", 1); // back in graveyard + assertHandCount(playerA, 2); + } + + @Test + public void test_Divination_No_Cast() { + setStrictChooseMode(true); + + addCard(Zone.GRAVEYARD, playerB, "Divination"); + addCard(Zone.HAND, playerA, plunder); + addCard(Zone.BATTLEFIELD, playerA, "Island", 4); + + castSpell(1, PhaseStep.UPKEEP, playerA, plunder, "Divination"); + setChoice(playerA, false); + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertGraveyardCount(playerA, plunder, 1); + assertGraveyardCount(playerB, "Divination", 1); // not moved + assertHandCount(playerA, 0); // no cast so no draw + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/vow/CemeteryIlluminatorTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/vow/CemeteryIlluminatorTest.java new file mode 100644 index 00000000000..01107363d6e --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/vow/CemeteryIlluminatorTest.java @@ -0,0 +1,105 @@ +package org.mage.test.cards.single.vow; + +import mage.constants.EmptyNames; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author xenohedron + */ +public class CemeteryIlluminatorTest extends CardTestPlayerBase { + + /* + Cemetery Illuminator {1}{U}{U} + Creature — Spirit + Flying + Whenever Cemetery Illuminator enters the battlefield or attacks, exile a card from a graveyard. + You may look at the top card of your library any time. + Once each turn, you may cast a spell from the top of your library if it shares a card type with a card exiled with Cemetery Illuminator. + */ + private static final String ci = "Cemetery Illuminator"; + + private static final String unicorn = "Lonesome Unicorn"; // 4W 3/3 with adventure 2W 2/2 token + private static final String rider = "Rider in Need"; + private static final String knight = "Knight Token"; + private static final String sorcery = "Sign in Blood"; + private static final String creature = "Walking Corpse"; + + @Test + public void testCreature() { + skipInitShuffling(); + addCard(Zone.LIBRARY, playerA, unicorn); + addCard(Zone.GRAVEYARD, playerA, creature); + addCard(Zone.HAND, playerA, ci); + addCard(Zone.BATTLEFIELD, playerA, "Tundra", 8); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, ci); + setChoice(playerA, creature); // to exile on ETB + + checkPlayableAbility("creature", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast " + unicorn, true); + checkPlayableAbility("sorcery", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast " + rider, false); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, unicorn); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertExileCount(playerA, creature, 1); + assertPermanentCount(playerA, ci, 1); + assertPermanentCount(playerA, unicorn, 1); + } + + @Test + public void testSorcery() { + skipInitShuffling(); + addCard(Zone.LIBRARY, playerA, unicorn); + addCard(Zone.GRAVEYARD, playerA, sorcery); + addCard(Zone.HAND, playerA, ci); + addCard(Zone.BATTLEFIELD, playerA, "Tundra", 8); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, ci); + setChoice(playerA, sorcery); // to exile on ETB + + checkPlayableAbility("creature", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast " + unicorn, false); + checkPlayableAbility("sorcery", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast " + rider, true); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, rider); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertExileCount(playerA, sorcery, 1); + assertPermanentCount(playerA, ci, 1); + assertPermanentCount(playerA, knight, 1); + } + + @Test + public void testMorph() { + String whetwheel = "Whetwheel"; // Artifact + skipInitShuffling(); + addCard(Zone.LIBRARY, playerA, whetwheel); + addCard(Zone.GRAVEYARD, playerA, creature); + addCard(Zone.HAND, playerA, ci); + addCard(Zone.BATTLEFIELD, playerA, "Tundra", 8); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, ci); + setChoice(playerA, creature); // to exile on ETB + + // test framework can't pick this up, but it works correctly + // checkPlayableAbility("artifact", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast " + whetwheel, false); + checkPlayableAbility("creature", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast " + whetwheel + " using Morph", true); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, whetwheel + " using Morph"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertExileCount(playerA, creature, 1); + assertPermanentCount(playerA, ci, 1); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPermanentCount(playerA, whetwheel, 0); + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/woc/CourtOfLocthwainTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/woc/CourtOfLocthwainTest.java index e27774ade10..03aa8b72042 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/woc/CourtOfLocthwainTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/woc/CourtOfLocthwainTest.java @@ -11,9 +11,9 @@ public class CourtOfLocthwainTest extends CardTestPlayerBase { * Court of Locthwain * {2}{B}{B} * Enchantment - * + *

* When Court of Locthwain enters the battlefield, you become the monarch. - * + *

* At the beginning of your upkeep, exile the top card of target opponent's library. You may play that card for as long as it remains exiled, and mana of any type can be spent to cast it. If you're the monarch, until end of turn, you may cast a spell from among cards exiled with Court of Locthwain without paying its mana cost. */ private static String court = "Court of Locthwain"; @@ -22,7 +22,7 @@ public class CourtOfLocthwainTest extends CardTestPlayerBase { * Armageddon * {3}{W} * Sorcery - * + *

* Destroy all lands. */ private static String armageddon = "Armageddon"; @@ -85,6 +85,35 @@ public class CourtOfLocthwainTest extends CardTestPlayerBase { assertTappedCount("Scrubland", true, 0); } + @Test + public void testMonarchChoiceCastMDFCForFree() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, court); + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears"); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4); + addCard(Zone.LIBRARY, playerB, "Fire // Ice"); + addCard(Zone.LIBRARY, playerB, "Island"); // playerB will draw it. + skipInitShuffling(); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, court); + + addTarget(playerA, playerB); // trigger target for turn 3 + checkExileCount("fire/ice got exiled", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Fire // Ice", 1); + + // We need to choose the proper AsThough, even if only one is valid. + setChoice(playerA, "Without paying manacost: "); + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Ice", "Grizzly Bears"); + + setStopAt(3, PhaseStep.BEGIN_COMBAT); + execute(); + + assertTappedCount("Swamp", true, 0); + assertGraveyardCount(playerB, "Fire // Ice", 1); + assertTappedCount("Grizzly Bears", true, 1); + assertHandCount(playerA, 1 + 1 + 1); // 1 regular draw, 1 with monarch trigger, 1 with Ice + } + @Test public void testMonarchChoiceCastForMana() { setStrictChooseMode(true); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/damage/ObNixilisCaptiveKingpinTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/damage/ObNixilisCaptiveKingpinTest.java new file mode 100644 index 00000000000..dbe7c8a74b3 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/damage/ObNixilisCaptiveKingpinTest.java @@ -0,0 +1,252 @@ +package org.mage.test.cards.triggers.damage; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestCommander4Players; + +public class ObNixilisCaptiveKingpinTest extends CardTestCommander4Players { + + // - 1 opponent dealt 1 damage -> Ob Nixilis triggers + // - 1 opponent dealt 2 damage -> No trigger + // - 2 opponents dealt 1 damage each -> Ob Nixilis triggers + // - 2 opponents dealt 2 damage each -> No trigger + // - opponent pays 1 life-> Ob Nixilis triggers + // - opponent pays 2 life -> No trigger + // - 1 opponent loses 1 life -> Ob Nixilis triggers + // - 1 opponent loses 2 life -> No trigger + // - 2 opponents lose 1 life each -> Ob Nixilis triggers + // - 2 opponents lose 2 life each -> No trigger + // - 2 opponents lose 1 and 2 life respectively -> No trigger + // - 1 opponent loses 1 and controller loses 2 life -> Ob Nixilis triggers + // - controller loses 1 life -> No trigger + + @Test + public void damageController1Point() { + addCard(Zone.BATTLEFIELD, playerA, "Ob Nixilis, Captive Kingpin", 1); + addCard(Zone.BATTLEFIELD, playerD, "Memnite"); + + attack(2, playerD, "Memnite", playerA); + + setStopAt(2, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertCounterCount("Ob Nixilis, Captive Kingpin", CounterType.P1P1, 0); + } + + @Test + public void damage1Opp1Point() { + addCard(Zone.BATTLEFIELD, playerA, "Ob Nixilis, Captive Kingpin", 1); + addCard(Zone.BATTLEFIELD, playerA, "Memnite"); + + attack(1, playerA, "Memnite", playerB); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertCounterCount("Ob Nixilis, Captive Kingpin", CounterType.P1P1, 1); + } + + @Test + public void damage1Opp2Points() { + addCard(Zone.BATTLEFIELD, playerA, "Ob Nixilis, Captive Kingpin", 1); + addCard(Zone.BATTLEFIELD, playerA, "Expedition Envoy"); + + attack(1, playerA, "Expedition Envoy", playerB); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertCounterCount("Ob Nixilis, Captive Kingpin", CounterType.P1P1, 0); + } + + @Test + public void damage2Opp1Point() { + addCard(Zone.BATTLEFIELD, playerA, "Ob Nixilis, Captive Kingpin", 1); + addCard(Zone.BATTLEFIELD, playerA, "Memnite", 2); + + attack(1, playerA, "Memnite", playerB); + attack(1, playerA, "Memnite", playerC); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertCounterCount("Ob Nixilis, Captive Kingpin", CounterType.P1P1, 1); + } + + @Test + public void damage2Opp2Points() { + addCard(Zone.BATTLEFIELD, playerA, "Ob Nixilis, Captive Kingpin", 1); + addCard(Zone.BATTLEFIELD, playerA, "Expedition Envoy", 2); + + attack(1, playerA, "Expedition Envoy", playerB); + attack(1, playerA, "Expedition Envoy", playerC); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertCounterCount("Ob Nixilis, Captive Kingpin", CounterType.P1P1, 0); + } + + @Test + public void damage2Opp1Point1Opp2Points() { + addCard(Zone.BATTLEFIELD, playerA, "Ob Nixilis, Captive Kingpin", 1); + addCard(Zone.BATTLEFIELD, playerA, "Expedition Envoy", 1); + addCard(Zone.BATTLEFIELD, playerA, "Memnite", 1); + + attack(1, playerA, "Expedition Envoy", playerB); + attack(1, playerA, "Memnite", playerC); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertCounterCount("Ob Nixilis, Captive Kingpin", CounterType.P1P1, 0); + } + + @Test + public void damage1Opp1PointCont2Points() { + addCard(Zone.BATTLEFIELD, playerA, "Ob Nixilis, Captive Kingpin", 1); + + addCard(Zone.BATTLEFIELD, playerD, "Expedition Envoy", 1); + addCard(Zone.BATTLEFIELD, playerA, "Memnite", 2); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 6); + + addCard(Zone.HAND, playerA, "Withstand Death", 1); + addCard(Zone.HAND, playerA, "Deadly Tempest", 1); + +// // Give Ob Nixiis indestructible so it can still trigger + addTarget(playerA, "Ob Nixilis, Captive Kingpin"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Withstand Death", true); + + // Destroy all creatures. + // Each player loses life equal to the number of creatures they controlled that were destroyed this way. + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Deadly Tempest", true); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertLife(playerA, currentGame.getStartingLife() - 2); + assertLife(playerD, currentGame.getStartingLife() - 1); + + assertCounterCount("Ob Nixilis, Captive Kingpin", CounterType.P1P1, 1); + } + + @Test + public void payLife1Opp1Point() { + addCard(Zone.BATTLEFIELD, playerA, "Ob Nixilis, Captive Kingpin", 1); + addCard(Zone.BATTLEFIELD, playerB, "Arid Mesa"); +// addCard(Zone.LIBRARY, playerA, "Mountain"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerB, "{T}, Pay 1 life"); + + addTarget(playerB, "Mountain"); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertCounterCount("Ob Nixilis, Captive Kingpin", CounterType.P1P1, 1); + } + + @Test + public void payLife1Opp2Point() { + addCard(Zone.BATTLEFIELD, playerA, "Ob Nixilis, Captive Kingpin", 1); + + addCard(Zone.BATTLEFIELD, playerB, "Forest", 2); + + // {2}, Pay 2 life: Draw a card. + addCard(Zone.BATTLEFIELD, playerB, "Book of Rass"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerB, "{2}, Pay 2 life"); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertCounterCount("Ob Nixilis, Captive Kingpin", CounterType.P1P1, 0); + } + + @Test + public void loseLife1Opp1Point() { + addCard(Zone.BATTLEFIELD, playerA, "Ob Nixilis, Captive Kingpin", 1); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); + + // {1}{B}, {T}: Target player loses 1 life. + addCard(Zone.BATTLEFIELD, playerA, "Acolyte of Xathrid"); + + addTarget(playerA, playerC); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{B}, {T}"); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertCounterCount("Ob Nixilis, Captive Kingpin", CounterType.P1P1, 1); + } + + @Test + public void loseLife1Opp2Point() { + addCard(Zone.BATTLEFIELD, playerA, "Ob Nixilis, Captive Kingpin", 1); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); + + // Target player draws two cards and loses 2 life. + addCard(Zone.HAND, playerA, "Blood Pact"); + + addTarget(playerA, playerD); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Blood Pact"); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertCounterCount("Ob Nixilis, Captive Kingpin", CounterType.P1P1, 0); + } + + @Test + public void loseLifeAll1Point() { + addCard(Zone.BATTLEFIELD, playerA, "Ob Nixilis, Captive Kingpin", 1); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); + + // {2}{W}: Target player gains 1 life. + // {2}{B}: Each player loses 1 life. + addCard(Zone.BATTLEFIELD, playerA, "Orzhov Guildmage"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}{B}"); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertCounterCount("Ob Nixilis, Captive Kingpin", CounterType.P1P1, 1); + } + + @Test + public void loseLifeAll2Point() { + addCard(Zone.BATTLEFIELD, playerA, "Ob Nixilis, Captive Kingpin", 1); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4); + + // Each player loses 2 life. You draw two cards. + addCard(Zone.HAND, playerA, "Crushing Disappointment"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Crushing Disappointment"); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertCounterCount("Ob Nixilis, Captive Kingpin", CounterType.P1P1, 0); + } + +} + 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 new file mode 100644 index 00000000000..182f315048c --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/VengefulTownsfolkTest.java @@ -0,0 +1,148 @@ +package org.mage.test.cards.triggers.dies; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * Tests the {@link mage.abilities.common.DiesOneOrMoreCreatureTriggeredAbility} batching. + * + * @author Susucr + */ +public class VengefulTownsfolkTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.v.VengefulTownsfolk Vengeful Townsfolk} + * Creature — Human Citizen + * Whenever one or more other creatures you control die, put a +1/+1 counter on Vengeful Townsfolk. + * 3/3 + */ + private static final String townsfolk = "Vengeful Townsfolk"; + + // Choose up to one creature. Destroy the rest. + private static final String duneblast = "Duneblast"; + + @Test + public void testOnTokens() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, townsfolk); + addCard(Zone.HAND, playerA, "Raise the Alarm", 1); // 2 1/1 tokens + addCard(Zone.HAND, playerB, duneblast, 1); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + addCard(Zone.BATTLEFIELD, playerB, "Plains", 3); + addCard(Zone.BATTLEFIELD, playerB, "Forest", 2); + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Raise the Alarm"); + setChoice(playerB, townsfolk); // do not destroy townsfolk + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, duneblast); + + setStopAt(2, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerB, duneblast, 1); + assertPermanentCount(playerA, 1 + 2); + assertPermanentCount(playerA, townsfolk, 1); + assertPowerToughness(playerA, townsfolk, 3 + 1, 3 + 1); + } + + @Test + public void testOnNonToken() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, townsfolk); + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears", 3); + addCard(Zone.HAND, playerB, duneblast, 1); + addCard(Zone.BATTLEFIELD, playerB, "Plains", 3); + addCard(Zone.BATTLEFIELD, playerB, "Forest", 2); + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2); + + setChoice(playerB, townsfolk); // do not destroy townsfolk + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, duneblast); + + setStopAt(2, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerB, duneblast, 1); + assertGraveyardCount(playerA, "Grizzly Bears", 3); + assertPermanentCount(playerA, 1); + assertPermanentCount(playerA, townsfolk, 1); + assertPowerToughness(playerA, townsfolk, 3 + 1, 3 + 1); + } + + @Test + public void testTwoSeparateDestroy() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, townsfolk); + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears", 1); + addCard(Zone.BATTLEFIELD, playerA, "Elite Vanguard", 1); + addCard(Zone.HAND, playerB, "Doom Blade", 2); + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 4); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Doom Blade", "Elite Vanguard"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Doom Blade", "Grizzly Bears"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerB, "Doom Blade", 2); + assertGraveyardCount(playerA, "Grizzly Bears", 1); + assertGraveyardCount(playerA, "Elite Vanguard", 1); + assertPermanentCount(playerA, 1); + assertPermanentCount(playerA, townsfolk, 1); + assertPowerToughness(playerA, townsfolk, 3 + 2, 3 + 2); + } + + @Test + public void testControllerMatters() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, townsfolk); + addCard(Zone.BATTLEFIELD, playerB, "Grizzly Bears", 1); + addCard(Zone.BATTLEFIELD, playerA, "Elite Vanguard", 1); + addCard(Zone.HAND, playerB, "Doom Blade", 2); + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 4); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Doom Blade", "Elite Vanguard"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Doom Blade", "Grizzly Bears"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerB, "Doom Blade", 2); + assertGraveyardCount(playerB, "Grizzly Bears", 1); + assertGraveyardCount(playerA, "Elite Vanguard", 1); + assertPermanentCount(playerA, 1); + assertPermanentCount(playerA, townsfolk, 1); + assertPowerToughness(playerA, townsfolk, 3 + 1, 3 + 1); + } + + @Test + public void testDoubleSacrifice() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, townsfolk); + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears"); + addCard(Zone.BATTLEFIELD, playerA, "Elite Vanguard"); + addCard(Zone.HAND, playerB, "Barter in Blood", 1); + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 4); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Barter in Blood"); + // sacrificing both, those are moved to graveyard at same time + setChoice(playerA, "Grizzly Bears"); + setChoice(playerA, "Elite Vanguard"); + + setStopAt(2, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerB, "Barter in Blood", 1); + assertGraveyardCount(playerA, "Grizzly Bears", 1); + assertGraveyardCount(playerA, "Elite Vanguard", 1); + assertPermanentCount(playerA, 1); + assertPermanentCount(playerA, townsfolk, 1); + assertPowerToughness(playerA, townsfolk, 3 + 1, 3 + 1); + } +} 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 f4886175e37..59083137054 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 @@ -1225,10 +1225,10 @@ public class TestPlayer implements Player { + ", " + (c.isTapped() ? "Tapped" : "Untapped") + getPrintableAliases(", [", c.getId(), "]") + (c.getAttachedTo() == null ? "" - : ", attached to " - + (game.getObject(c.getAttachedTo()) == null - ? game.getPlayer(c.getAttachedTo()).getName() - : game.getObject(c.getAttachedTo()).getIdName())))) + : ", attached to " + + (game.getObject(c.getAttachedTo()) == null + ? game.getPlayer(c.getAttachedTo()).getName() + : game.getObject(c.getAttachedTo()).getIdName())))) .sorted() .collect(Collectors.toList()); @@ -3833,6 +3833,16 @@ public class TestPlayer implements Player { computerPlayer.setDrawsOnOpponentsTurn(drawsOnOpponentsTurn); } + @Override + public boolean canPlotFromTopOfLibrary() { + return computerPlayer.canPlotFromTopOfLibrary(); + } + + @Override + public void setPlotFromTopOfLibrary(boolean canPlotFromTopOfLibrary) { + computerPlayer.setPlotFromTopOfLibrary(canPlotFromTopOfLibrary); + } + @Override public boolean isDrawsOnOpponentsTurn() { return computerPlayer.isDrawsOnOpponentsTurn(); diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/performance/RulesFormatTest.java b/Mage.Tests/src/test/java/org/mage/test/serverside/performance/RulesFormatTest.java new file mode 100644 index 00000000000..53d97767b2a --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/performance/RulesFormatTest.java @@ -0,0 +1,50 @@ +package org.mage.test.serverside.performance; + +import mage.MageObject; +import mage.cards.repository.CardScanner; +import mage.util.CardUtil; +import org.junit.Ignore; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author JayDi85 + */ +public class RulesFormatTest { + + @Test + @Ignore // debug only, can be slow (10+ secs) + public void test_InfiniteFreezeOnStringReplace_Fast() { + // use case: in some point of time java's string replace code from GainAbilityAttachedEffect can freeze forever + // details: https://github.com/magefree/mage/issues/11285#issuecomment-2011326865 + // status: can't reproduce original bug, maybe regexp freeze related to JRE versions/builds + + CardScanner.scan(); + CardScanner.getAllCards().forEach(card -> { + List possibleObjectNames = new ArrayList<>(); + // any card names + CardUtil.getObjectPartsAsObjects(card) + .stream() + .map(MageObject::getName) + .forEach(possibleObjectNames::add); + // all names from GainAbilityAttachedEffect + possibleObjectNames.add("creature"); + possibleObjectNames.add("permanent"); + possibleObjectNames.add("land"); + possibleObjectNames.add("planeswalker"); + + CardUtil.getObjectPartsAsObjects(card) + .forEach(part -> { + part.getAbilities().forEach(ability -> { + possibleObjectNames.forEach(name -> { + // simulate replacement code from GainAbilityAttachedEffect::getText + String sourceName = "This " + name; + ability.getRule(sourceName); + }); + }); + }); + }); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/sets/BoosterGenerationTest.java b/Mage.Tests/src/test/java/org/mage/test/sets/BoosterGenerationTest.java index b932679cb4e..65bec496a91 100644 --- a/Mage.Tests/src/test/java/org/mage/test/sets/BoosterGenerationTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/sets/BoosterGenerationTest.java @@ -14,6 +14,7 @@ import mage.game.draft.RemixedSet; import mage.sets.*; import mage.util.CardUtil; import org.junit.Assert; +import static org.junit.Assert.*; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; @@ -22,8 +23,6 @@ import org.mage.test.serverside.base.MageTestPlayerBase; import java.util.*; import java.util.stream.Collectors; -import static org.junit.Assert.*; - /** * @author nigelzor, JayDi85 */ @@ -551,7 +550,7 @@ public class BoosterGenerationTest extends MageTestPlayerBase { @Ignore // debug only: collect info about cards in boosters, see https://github.com/magefree/mage/issues/8081 @Test public void test_CollectBoosterStats() { - ExpansionSet setToAnalyse = FallenEmpires.getInstance(); + ExpansionSet setToAnalyse = OutlawsOfThunderJunction.getInstance(); int openBoosters = 10000; Map resRatio = new HashMap<>(); @@ -560,7 +559,7 @@ public class BoosterGenerationTest extends MageTestPlayerBase { List booster = setToAnalyse.createBooster(); totalCards += booster.size(); booster.forEach(card -> { - String code = String.format("%s %s", card.getRarity().getCode(), card.getName()); + String code = String.format("%s %s %s", card.getExpansionSetCode(), card.getRarity().getCode(), card.getName()); resRatio.putIfAbsent(code, 0); resRatio.computeIfPresent(code, (u, count) -> count + 1); }); diff --git a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java index f1f210aa95c..fd99a43db86 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 = "WHO;LTC;LCI;LCC;REX"; // check ability text due mtgjson, can use multiple sets like MAT;CMD or * for all + private static final String FULL_ABILITIES_CHECK_SET_CODES = "OTJ;BIG;OTC"; // 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 AUTO_FIX_SAMPLE_DECKS = false; // debug only: auto-fix sample decks by test_checkSampleDecks test run diff --git a/Mage/src/main/java/mage/MageIdentifier.java b/Mage/src/main/java/mage/MageIdentifier.java index 63592d375c1..cc10c5eb0ca 100644 --- a/Mage/src/main/java/mage/MageIdentifier.java +++ b/Mage/src/main/java/mage/MageIdentifier.java @@ -20,12 +20,9 @@ public enum MageIdentifier { // "Once each turn, you may cast an instant or sorcery spell from the top of your library." // CastFromGraveyardOnceWatcher, - CemeteryIlluminatorWatcher, - GisaAndGeralfWatcher, - DanithaNewBenaliasLightWatcher, + OnceEachTurnCastWatcher, HaukensInsightWatcher, IntrepidPaleontologistWatcher, - KaradorGhostChieftainWatcher, KessDissidentMageWatcher, MuldrothaTheGravetideWatcher, ShareTheSpoilsWatcher, @@ -33,8 +30,6 @@ public enum MageIdentifier { GlimpseTheCosmosWatcher, SerraParagonWatcher, OneWithTheMultiverseWatcher("Without paying manacost"), - JohannApprenticeSorcererWatcher, - AssembleThePlayersWatcher, KaghaShadowArchdruidWatcher, CourtOfLocthwainWatcher("Without paying manacost"), LaraCroftTombRaiderWatcher, diff --git a/Mage/src/main/java/mage/MageObject.java b/Mage/src/main/java/mage/MageObject.java index 4656944e022..376fd4abced 100644 --- a/Mage/src/main/java/mage/MageObject.java +++ b/Mage/src/main/java/mage/MageObject.java @@ -167,11 +167,19 @@ public interface MageObject extends MageItem, Serializable, Copyable void setZoneChangeCounter(int value, Game game); default boolean isHistoric(Game game) { - return getCardType(game).contains(CardType.ARTIFACT) - || getSuperType(game).contains(SuperType.LEGENDARY) + return isArtifact(game) + || isLegendary(game) || hasSubtype(SubType.SAGA, game); } + default boolean isOutlaw(Game game) { + return hasSubtype(SubType.ASSASSIN, game) + || hasSubtype(SubType.MERCENARY, game) + || hasSubtype(SubType.PIRATE, game) + || hasSubtype(SubType.ROGUE, game) + || hasSubtype(SubType.WARLOCK, game); + } + default boolean isCreature() { return isCreature(null); } diff --git a/Mage/src/main/java/mage/MageObjectReference.java b/Mage/src/main/java/mage/MageObjectReference.java index 22fdf83d15e..17cb40cf71f 100644 --- a/Mage/src/main/java/mage/MageObjectReference.java +++ b/Mage/src/main/java/mage/MageObjectReference.java @@ -57,10 +57,12 @@ public class MageObjectReference implements Comparable, Ser this.zoneChangeCounter = -1; } + @Deprecated // cause of many bugs, see issue #10479 public MageObjectReference(Ability source) { this(source, 0); } + @Deprecated // cause of many bugs, see issue #10479 public MageObjectReference(Ability source, int modifier) { this.sourceId = source.getSourceId(); this.zoneChangeCounter = source.getSourceObjectZoneChangeCounter() + modifier; diff --git a/Mage/src/main/java/mage/abilities/Ability.java b/Mage/src/main/java/mage/abilities/Ability.java index 1c285a8e6e2..d538c8fe84d 100644 --- a/Mage/src/main/java/mage/abilities/Ability.java +++ b/Mage/src/main/java/mage/abilities/Ability.java @@ -514,6 +514,14 @@ public interface Ability extends Controllable, Serializable { */ Ability withFirstModeFlavorWord(String flavorWord); + /** + * Sets cost word for first mode + * + * @param cost + * @return + */ + Ability withFirstModeCost(Cost cost); + /** * Creates the message about the ability casting/triggering/activating to * post in the game log before the ability resolves. diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index 0616397eb6d..8665ab1d665 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -52,37 +52,37 @@ public abstract class AbilityImpl implements Ability { private static final List emptyAbilities = new ArrayList<>(); protected UUID id; - protected UUID originalId; // TODO: delete originalId??? + private UUID originalId; // TODO: delete originalId??? protected AbilityType abilityType; protected UUID controllerId; protected UUID sourceId; private final ManaCosts manaCosts; private final ManaCosts manaCostsToPay; private final Costs costs; - protected Modes modes; // access to it by GetModes only (it can be overridden by some abilities) + private final Modes modes; // access to it by GetModes only (it can be overridden by some abilities) protected Zone zone; protected String name; protected AbilityWord abilityWord; protected String flavorWord; protected boolean usesStack = true; - protected boolean ruleAtTheTop = false; - protected boolean ruleVisible = true; - protected boolean ruleAdditionalCostsVisible = true; + private boolean ruleAtTheTop = false; + private boolean ruleVisible = true; + private boolean ruleAdditionalCostsVisible = true; protected boolean activated = false; - protected boolean worksFaceDown = false; - protected boolean worksPhasedOut = false; - protected int sourceObjectZoneChangeCounter; - protected List watchers = new ArrayList<>(); // access to it by GetWatchers only (it can be overridden by some abilities) - protected List subAbilities = null; - protected boolean canFizzle = true; - protected TargetAdjuster targetAdjuster = null; - protected CostAdjuster costAdjuster = null; - protected List hints = new ArrayList<>(); + private boolean worksFaceDown = false; + private boolean worksPhasedOut = false; + private int sourceObjectZoneChangeCounter; + private List watchers = new ArrayList<>(); // access to it by GetWatchers only (it can be overridden by some abilities) + private List subAbilities = null; + private boolean canFizzle = true; // for Gilded Drake + private TargetAdjuster targetAdjuster = null; + private CostAdjuster costAdjuster = null; + private List hints = new ArrayList<>(); protected List icons = new ArrayList<>(); - protected Outcome customOutcome = null; // uses for AI decisions instead effects - protected MageIdentifier identifier = MageIdentifier.Default; // used to identify specific ability (e.g. to match with corresponding watcher) - protected String appendToRule = null; - protected int sourcePermanentTransformCount = 0; + private Outcome customOutcome = null; // uses for AI decisions instead effects + private MageIdentifier identifier = MageIdentifier.Default; // used to identify specific ability (e.g. to match with corresponding watcher) + private String appendToRule = null; + private int sourcePermanentTransformCount = 0; private Map costsTagMap = null; protected AbilityImpl(AbilityType abilityType, Zone zone) { @@ -311,6 +311,16 @@ public abstract class AbilityImpl implements Ability { return false; } + // apply mode costs if they have them + for (UUID modeId : this.getModes().getSelectedModes()) { + Cost cost = this.getModes().get(modeId).getCost(); + if (cost instanceof ManaCost) { + this.addManaCostsToPay((ManaCost) cost.copy()); + } else if (cost != null) { + this.costs.add(cost.copy()); + } + } + // unit tests only: it allows to add targets/choices by two ways: // 1. From cast/activate command params (process it here) // 2. From single addTarget/setChoice, it's a preffered method for tests (process it in normal choose dialogs like human player) @@ -429,6 +439,7 @@ public abstract class AbilityImpl implements Ability { case BESTOW: case MORPH: case DISGUISE: + case PLOT: // from Snapcaster Mage: // If you cast a spell from a graveyard using its flashback ability, you can't pay other alternative costs // (such as that of Foil). (2018-12-07) @@ -521,7 +532,7 @@ public abstract class AbilityImpl implements Ability { String message = controller.getLogName() + " announces a value of " + xValue + " (" + variableCost.getActionText() + ')' + CardUtil.getSourceLogName(game, this); announceString.append(message); - setCostsTag("X",xValue); + setCostsTag("X", xValue); } } return announceString.toString(); @@ -626,7 +637,7 @@ public abstract class AbilityImpl implements Ability { } addManaCostsToPay(new ManaCostsImpl<>(manaString.toString())); getManaCostsToPay().setX(xValue * xValueMultiplier, amountMana); - setCostsTag("X",xValue * xValueMultiplier); + setCostsTag("X", xValue * xValueMultiplier); } variableManaCost.setPaid(); } @@ -718,8 +729,9 @@ public abstract class AbilityImpl implements Ability { public Map getCostsTagMap() { return costsTagMap; } - public void setCostsTag(String tag, Object value){ - if (costsTagMap == null){ + + public void setCostsTag(String tag, Object value) { + if (costsTagMap == null) { costsTagMap = new HashMap<>(); } costsTagMap.put(tag, value); @@ -1139,6 +1151,12 @@ public abstract class AbilityImpl implements Ability { return this; } + @Override + public Ability withFirstModeCost(Cost cost) { + this.modes.getMode().withCost(cost); + return this; + } + @Override public String getGameLogMessage(Game game) { if (game.isSimulation()) { diff --git a/Mage/src/main/java/mage/abilities/ActivatedAbilityImpl.java b/Mage/src/main/java/mage/abilities/ActivatedAbilityImpl.java index a918ff8ea75..caa2feaf10e 100644 --- a/Mage/src/main/java/mage/abilities/ActivatedAbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/ActivatedAbilityImpl.java @@ -238,11 +238,11 @@ public abstract class ActivatedAbilityImpl extends AbilityImpl implements Activa protected ActivationInfo getActivationInfo(Game game) { Integer turnNum = (Integer) game.getState() - .getValue(CardUtil.getCardZoneString("activationsTurn" + originalId, sourceId, game)); + .getValue(CardUtil.getCardZoneString("activationsTurn" + getOriginalId(), sourceId, game)); Integer activationCount = (Integer) game.getState() - .getValue(CardUtil.getCardZoneString("activationsCount" + originalId, sourceId, game)); + .getValue(CardUtil.getCardZoneString("activationsCount" + getOriginalId(), sourceId, game)); Integer totalActivations = (Integer) game.getState() - .getValue(CardUtil.getCardZoneString("totalActivations" + originalId, sourceId, game)); + .getValue(CardUtil.getCardZoneString("totalActivations" + getOriginalId(), sourceId, game)); if (turnNum == null || activationCount == null || totalActivations == null) { return null; } @@ -251,11 +251,11 @@ public abstract class ActivatedAbilityImpl extends AbilityImpl implements Activa protected void setActivationInfo(ActivationInfo activationInfo, Game game) { game.getState().setValue(CardUtil - .getCardZoneString("activationsTurn" + originalId, sourceId, game), activationInfo.turnNum); + .getCardZoneString("activationsTurn" + getOriginalId(), sourceId, game), activationInfo.turnNum); game.getState().setValue(CardUtil - .getCardZoneString("activationsCount" + originalId, sourceId, game), activationInfo.activationCounter); + .getCardZoneString("activationsCount" + getOriginalId(), sourceId, game), activationInfo.activationCounter); game.getState().setValue(CardUtil - .getCardZoneString("totalActivations" + originalId, sourceId, game), activationInfo.totalActivations); + .getCardZoneString("totalActivations" + getOriginalId(), sourceId, game), activationInfo.totalActivations); } @Override diff --git a/Mage/src/main/java/mage/abilities/Mode.java b/Mage/src/main/java/mage/abilities/Mode.java index 427e18bcfaf..8c6e6747e4a 100644 --- a/Mage/src/main/java/mage/abilities/Mode.java +++ b/Mage/src/main/java/mage/abilities/Mode.java @@ -1,5 +1,6 @@ package mage.abilities; +import mage.abilities.costs.Cost; import mage.abilities.effects.Effect; import mage.abilities.effects.Effects; import mage.target.Target; @@ -17,6 +18,7 @@ public class Mode implements Serializable { protected final Targets targets; protected final Effects effects; protected String flavorWord; + protected Cost cost = null; /** * Optional Tag to distinguish this mode from others. * In the case of modes that players can only choose once, @@ -39,6 +41,7 @@ public class Mode implements Serializable { this.effects = mode.effects.copy(); this.flavorWord = mode.flavorWord; this.modeTag = mode.modeTag; + this.cost = mode.cost != null ? mode.cost.copy() : null; } public UUID setRandomId() { @@ -107,4 +110,13 @@ public class Mode implements Serializable { this.flavorWord = flavorWord; return this; } + + public Mode withCost(Cost cost) { + this.cost = cost; + return this; + } + + public Cost getCost() { + return cost; + } } diff --git a/Mage/src/main/java/mage/abilities/Modes.java b/Mage/src/main/java/mage/abilities/Modes.java index 4b0b117cefc..c612205e1fc 100644 --- a/Mage/src/main/java/mage/abilities/Modes.java +++ b/Mage/src/main/java/mage/abilities/Modes.java @@ -584,7 +584,14 @@ public class Modes extends LinkedHashMap implements Copyable sb.append("
"); for (Mode mode : this.values()) { - sb.append("&bull "); + if (mode.getCost() != null) { + // for Spree + sb.append("+ "); + sb.append(mode.getCost().getText()); + sb.append(" — "); + } else { + sb.append("&bull "); + } sb.append(mode.getEffects().getTextStartingUpperCase(mode)); sb.append("
"); } diff --git a/Mage/src/main/java/mage/abilities/SpecialActions.java b/Mage/src/main/java/mage/abilities/SpecialActions.java index d996d73e3c2..173ea2bc6b7 100644 --- a/Mage/src/main/java/mage/abilities/SpecialActions.java +++ b/Mage/src/main/java/mage/abilities/SpecialActions.java @@ -32,7 +32,7 @@ public class SpecialActions extends AbilitiesImpl { LinkedHashMap controlledBy = new LinkedHashMap<>(); for (SpecialAction action : this) { if (action.isControlledBy(controllerId) && action.isManaAction() == manaAction) { - controlledBy.put(action.id, action); + controlledBy.put(action.getId(), action); } } return controlledBy; diff --git a/Mage/src/main/java/mage/abilities/SpellAbility.java b/Mage/src/main/java/mage/abilities/SpellAbility.java index dd7ad11f98c..08b51268916 100644 --- a/Mage/src/main/java/mage/abilities/SpellAbility.java +++ b/Mage/src/main/java/mage/abilities/SpellAbility.java @@ -358,6 +358,6 @@ public class SpellAbility extends ActivatedAbilityImpl { } public void setId(UUID idToUse) { - this.id = idToUse; + this.id = idToUse; // TODO: research, why is it needed } } diff --git a/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java b/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java index b654a610e3f..e81625ad36a 100644 --- a/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java @@ -75,7 +75,7 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge return; } game.getState().setValue(CardUtil.getCardZoneString( - "lastTurnTriggered" + originalId, sourceId, game + "lastTurnTriggered" + getOriginalId(), sourceId, game ), game.getTurnNum()); } @@ -101,7 +101,7 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge return true; } Integer lastTurnTriggered = (Integer) game.getState().getValue( - CardUtil.getCardZoneString("lastTurnTriggered" + originalId, sourceId, game) + CardUtil.getCardZoneString("lastTurnTriggered" + getOriginalId(), sourceId, game) ); return lastTurnTriggered == null || lastTurnTriggered != game.getTurnNum(); } @@ -112,7 +112,7 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge return false; } Integer lastTurnUsed = (Integer) game.getState().getValue( - CardUtil.getCardZoneString("lastTurnUsed" + originalId, sourceId, game) + CardUtil.getCardZoneString("lastTurnUsed" + getOriginalId(), sourceId, game) ); return lastTurnUsed != null && lastTurnUsed == game.getTurnNum(); } @@ -165,7 +165,7 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge } if (doOnlyOnceEachTurn) { game.getState().setValue(CardUtil.getCardZoneString( - "lastTurnUsed" + originalId, sourceId, game + "lastTurnUsed" + getOriginalId(), sourceId, game ), game.getTurnNum()); } //20091005 - 603.4 diff --git a/Mage/src/main/java/mage/abilities/common/AttacksWhileSaddledTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/AttacksWhileSaddledTriggeredAbility.java new file mode 100644 index 00000000000..a1e422f800b --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/AttacksWhileSaddledTriggeredAbility.java @@ -0,0 +1,37 @@ +package mage.abilities.common; + +import mage.abilities.effects.Effect; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; + +import java.util.Optional; + +/** + * @author TheElk801 + */ +public class AttacksWhileSaddledTriggeredAbility extends AttacksTriggeredAbility { + + public AttacksWhileSaddledTriggeredAbility(Effect effect) { + super(effect); + this.setTriggerPhrase("Whenever {this} attacks while saddled, "); + } + + private AttacksWhileSaddledTriggeredAbility(final AttacksWhileSaddledTriggeredAbility ability) { + super(ability); + } + + @Override + public AttacksWhileSaddledTriggeredAbility copy() { + return new AttacksWhileSaddledTriggeredAbility(this); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return super.checkTrigger(event, game) + && Optional + .ofNullable(getSourcePermanentIfItStillExists(game)) + .map(Permanent::isSaddled) + .orElse(false); + } +} diff --git a/Mage/src/main/java/mage/abilities/common/BecomesPlottedSourceTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/BecomesPlottedSourceTriggeredAbility.java new file mode 100644 index 00000000000..568db97b8b2 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/BecomesPlottedSourceTriggeredAbility.java @@ -0,0 +1,45 @@ +package mage.abilities.common; + +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.Effect; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; + +/** + * @author Susucr + */ +public class BecomesPlottedSourceTriggeredAbility extends TriggeredAbilityImpl { + + public BecomesPlottedSourceTriggeredAbility(Effect effect, boolean optional) { + super(Zone.EXILED, effect, optional); + setTriggerPhrase("When {this} becomes plotted, "); + replaceRuleText = true; + } + + public BecomesPlottedSourceTriggeredAbility(Effect effect) { + this(effect, false); + } + + protected BecomesPlottedSourceTriggeredAbility(final BecomesPlottedSourceTriggeredAbility ability) { + super(ability); + } + + @Override + public BecomesPlottedSourceTriggeredAbility copy() { + return new BecomesPlottedSourceTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.BECOME_PLOTTED; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (event.getTargetId().equals(this.getSourceId())) { + return true; + } + return false; + } +} \ No newline at end of file diff --git a/Mage/src/main/java/mage/abilities/common/BecomesTappedOneOrMoreTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/BecomesTappedOneOrMoreTriggeredAbility.java index 8ca7082e7c6..7b96abf685c 100644 --- a/Mage/src/main/java/mage/abilities/common/BecomesTappedOneOrMoreTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/BecomesTappedOneOrMoreTriggeredAbility.java @@ -40,7 +40,7 @@ public class BecomesTappedOneOrMoreTriggeredAbility extends TriggeredAbilityImpl public boolean checkTrigger(GameEvent event, Game game) { TappedBatchEvent batchEvent = (TappedBatchEvent) event; return batchEvent - .getTargets() + .getTargetIds() .stream() .map(game::getPermanent) .anyMatch(p -> filter.match(p, getControllerId(), this, game)); diff --git a/Mage/src/main/java/mage/abilities/common/BecomesTargetAnyTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/BecomesTargetAnyTriggeredAbility.java index e65f68dd59a..9e5a7490e98 100644 --- a/Mage/src/main/java/mage/abilities/common/BecomesTargetAnyTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/BecomesTargetAnyTriggeredAbility.java @@ -74,7 +74,7 @@ public class BecomesTargetAnyTriggeredAbility extends TriggeredAbilityImpl { if (targetingObject == null || !filterStack.match(targetingObject, getControllerId(), this, game)) { return false; } - if (CardUtil.checkTargetedEventAlreadyUsed(this.id.toString(), targetingObject, event, game)) { + if (CardUtil.checkTargetedEventAlreadyUsed(this.getId().toString(), targetingObject, event, game)) { return false; } switch (setTargetPointer) { diff --git a/Mage/src/main/java/mage/abilities/common/BecomesTargetAttachedTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/BecomesTargetAttachedTriggeredAbility.java index b4098303ae0..2995cacb11b 100644 --- a/Mage/src/main/java/mage/abilities/common/BecomesTargetAttachedTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/BecomesTargetAttachedTriggeredAbility.java @@ -58,7 +58,7 @@ public class BecomesTargetAttachedTriggeredAbility extends TriggeredAbilityImpl if (targetingObject == null || !filter.match(targetingObject, getControllerId(), this, game)) { return false; } - if (CardUtil.checkTargetedEventAlreadyUsed(this.id.toString(), targetingObject, event, game)) { + if (CardUtil.checkTargetedEventAlreadyUsed(this.getId().toString(), targetingObject, event, game)) { return false; } switch (setTargetPointer) { diff --git a/Mage/src/main/java/mage/abilities/common/BecomesTargetControllerTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/BecomesTargetControllerTriggeredAbility.java index ccb9a6dde2f..c31b39b22d2 100644 --- a/Mage/src/main/java/mage/abilities/common/BecomesTargetControllerTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/BecomesTargetControllerTriggeredAbility.java @@ -67,7 +67,7 @@ public class BecomesTargetControllerTriggeredAbility extends TriggeredAbilityImp if (targetingObject == null || !filterStack.match(targetingObject, getControllerId(), this, game)) { return false; } - if (CardUtil.checkTargetedEventAlreadyUsed(this.id.toString(), targetingObject, event, game)) { + if (CardUtil.checkTargetedEventAlreadyUsed(this.getId().toString(), targetingObject, event, game)) { return false; } switch (setTargetPointer) { diff --git a/Mage/src/main/java/mage/abilities/common/BecomesTargetSourceTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/BecomesTargetSourceTriggeredAbility.java index e1d36235246..0b400eb3ed3 100644 --- a/Mage/src/main/java/mage/abilities/common/BecomesTargetSourceTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/BecomesTargetSourceTriggeredAbility.java @@ -61,7 +61,7 @@ public class BecomesTargetSourceTriggeredAbility extends TriggeredAbilityImpl { if (targetingObject == null || !filter.match(targetingObject, getControllerId(), this, game)) { return false; } - if (CardUtil.checkTargetedEventAlreadyUsed(this.id.toString(), targetingObject, event, game)) { + if (CardUtil.checkTargetedEventAlreadyUsed(this.getId().toString(), targetingObject, event, game)) { return false; } switch (setTargetPointer) { diff --git a/Mage/src/main/java/mage/abilities/common/CastFromGraveyardOnceStaticAbility.java b/Mage/src/main/java/mage/abilities/common/CastFromGraveyardOnceEachTurnAbility.java similarity index 52% rename from Mage/src/main/java/mage/abilities/common/CastFromGraveyardOnceStaticAbility.java rename to Mage/src/main/java/mage/abilities/common/CastFromGraveyardOnceEachTurnAbility.java index cef0a26ddc2..b80276d38d7 100644 --- a/Mage/src/main/java/mage/abilities/common/CastFromGraveyardOnceStaticAbility.java +++ b/Mage/src/main/java/mage/abilities/common/CastFromGraveyardOnceEachTurnAbility.java @@ -3,6 +3,7 @@ package mage.abilities.common; import mage.MageIdentifier; import mage.MageObjectReference; import mage.abilities.Ability; +import mage.abilities.SpellAbility; import mage.abilities.effects.AsThoughEffectImpl; import mage.cards.Card; import mage.constants.*; @@ -10,6 +11,7 @@ import mage.filter.FilterCard; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; +import mage.players.Player; import mage.watchers.Watcher; import java.util.HashSet; @@ -18,26 +20,26 @@ import java.util.UUID; /** * Once during each of your turns, you may cast... from your graveyard - * + *

* See Lurrus of the Dream Den and Rivaz of the Claw * * @author weirddan455 */ -public class CastFromGraveyardOnceStaticAbility extends SimpleStaticAbility { +public class CastFromGraveyardOnceEachTurnAbility extends SimpleStaticAbility { - public CastFromGraveyardOnceStaticAbility(FilterCard filter, String text) { - super(new CastFromGraveyardOnceEffect(filter, text)); + public CastFromGraveyardOnceEachTurnAbility(FilterCard filter) { + super(new CastFromGraveyardOnceEffect(filter)); this.addWatcher(new CastFromGraveyardOnceWatcher()); this.setIdentifier(MageIdentifier.CastFromGraveyardOnceWatcher); } - private CastFromGraveyardOnceStaticAbility(final CastFromGraveyardOnceStaticAbility ability) { + private CastFromGraveyardOnceEachTurnAbility(final CastFromGraveyardOnceEachTurnAbility ability) { super(ability); } @Override - public CastFromGraveyardOnceStaticAbility copy() { - return new CastFromGraveyardOnceStaticAbility(this); + public CastFromGraveyardOnceEachTurnAbility copy() { + return new CastFromGraveyardOnceEachTurnAbility(this); } } @@ -45,10 +47,11 @@ class CastFromGraveyardOnceEffect extends AsThoughEffectImpl { private final FilterCard filter; - CastFromGraveyardOnceEffect(FilterCard filter, String text) { + CastFromGraveyardOnceEffect(FilterCard filter) { super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.Benefit); this.filter = filter; - this.staticText = text; + this.staticText = "Once during each of your turns, you may cast " + filter.getMessage() + + (filter.getMessage().contains("from your graveyard") ? "" : " from your graveyard"); } private CastFromGraveyardOnceEffect(final CastFromGraveyardOnceEffect effect) { @@ -68,19 +71,30 @@ class CastFromGraveyardOnceEffect extends AsThoughEffectImpl { @Override public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { - if (source.isControlledBy(affectedControllerId) - && Zone.GRAVEYARD.equals(game.getState().getZone(objectId)) - && game.isActivePlayer(affectedControllerId)) { - Card card = game.getCard(objectId); - Permanent sourceObject = source.getSourcePermanentIfItStillExists(game); - if (card != null && sourceObject != null - && card.isOwnedBy(affectedControllerId) - && card.getSpellAbility() != null - && card.getSpellAbility().spellCanBeActivatedRegularlyNow(affectedControllerId, game) - && filter.match(card, affectedControllerId, source, game)) { - CastFromGraveyardOnceWatcher watcher = game.getState().getWatcher(CastFromGraveyardOnceWatcher.class); - return watcher != null && watcher.abilityNotUsed(new MageObjectReference(sourceObject, game)); + throw new IllegalArgumentException("Wrong code usage: can't call applies method on empty affectedAbility"); + } + + @Override + public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) { + Player controller = game.getPlayer(source.getControllerId()); + Permanent sourcePermanent = source.getSourcePermanentIfItStillExists(game); + CastFromGraveyardOnceWatcher watcher = game.getState().getWatcher(CastFromGraveyardOnceWatcher.class); + if (controller == null || sourcePermanent == null || watcher == null) { + return false; + } + if (game.isActivePlayer(playerId) // only during your turn + && source.isControlledBy(playerId) // only you may cast + && Zone.GRAVEYARD.equals(game.getState().getZone(objectId)) // from graveyard + && affectedAbility instanceof SpellAbility // characteristics to check + && watcher.abilityNotUsed(new MageObjectReference(sourcePermanent, game)) // once per turn + ) { + SpellAbility spellAbility = (SpellAbility) affectedAbility; + Card cardToCheck = spellAbility.getCharacteristics(game); + if (spellAbility.getManaCosts().isEmpty()) { + return false; } + return spellAbility.spellCanBeActivatedRegularlyNow(playerId, game) + && filter.match(cardToCheck, playerId, source, game); } return false; } diff --git a/Mage/src/main/java/mage/abilities/common/CommittedCrimeTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/CommittedCrimeTriggeredAbility.java new file mode 100644 index 00000000000..c04fcee8b4b --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/CommittedCrimeTriggeredAbility.java @@ -0,0 +1,122 @@ +package mage.abilities.common; + +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.Effect; +import mage.constants.Zone; +import mage.game.Controllable; +import mage.game.Game; +import mage.game.Ownerable; +import mage.game.events.GameEvent; +import mage.game.stack.Spell; +import mage.game.stack.StackObject; +import mage.util.CardUtil; + +import java.util.Objects; +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public class CommittedCrimeTriggeredAbility extends TriggeredAbilityImpl { + + public CommittedCrimeTriggeredAbility(Effect effect) { + this(effect, false); + } + + public CommittedCrimeTriggeredAbility(Effect effect, boolean optional) { + this(Zone.BATTLEFIELD, effect, optional); + } + + public CommittedCrimeTriggeredAbility(Zone zone, Effect effect, boolean optional) { + super(zone, effect, optional); + setTriggerPhrase("Whenever you commit a crime, "); + } + + protected CommittedCrimeTriggeredAbility(final CommittedCrimeTriggeredAbility ability) { + super(ability); + } + + @Override + public CommittedCrimeTriggeredAbility copy() { + return new CommittedCrimeTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + switch (event.getType()) { + case SPELL_CAST: + case ACTIVATED_ABILITY: + case TRIGGERED_ABILITY: + return true; + default: + return false; + } + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return isControlledBy(getCriminal(event, game)); + } + + public static UUID getCriminal(GameEvent event, Game game) { + UUID controllerId; + Ability ability; + switch (event.getType()) { + case SPELL_CAST: + Spell spell = game.getSpell(event.getTargetId()); + if (spell == null) { + return null; + } + controllerId = spell.getControllerId(); + ability = spell.getSpellAbility(); + break; + case ACTIVATED_ABILITY: + case TRIGGERED_ABILITY: + StackObject stackObject = game.getStack().getStackObject(event.getTargetId()); + if (stackObject == null) { + return null; + } + controllerId = stackObject.getControllerId(); + ability = stackObject.getStackAbility(); + break; + default: + return null; + } + if (controllerId == null || ability == null) { + return null; + } + Set opponents = game.getOpponents(controllerId); + Set targets = CardUtil.getAllSelectedTargets(ability, game); + // an opponent + if (targets + .stream() + .anyMatch(opponents::contains) + // an opponent's permanent + || targets + .stream() + .map(game::getPermanent) + .filter(Objects::nonNull) + .map(Controllable::getControllerId) + .anyMatch(opponents::contains) + // an opponent's spell or ability + || targets + .stream() + .map(game.getStack()::getStackObject) + .filter(Objects::nonNull) + .map(Controllable::getControllerId) + .anyMatch(opponents::contains) + // a card in an opponent's graveyard + || targets + .stream() + .filter(uuid -> Zone.GRAVEYARD.match(game.getState().getZone(uuid))) + .map(game::getCard) + .filter(Objects::nonNull) + .map(Ownerable::getOwnerId) + .anyMatch(opponents::contains)) { + return controllerId; + } + return null; + } +} diff --git a/Mage/src/main/java/mage/abilities/common/DealsCombatDamageEquippedTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DealsCombatDamageEquippedTriggeredAbility.java index 82877928a76..9620d538d1c 100644 --- a/Mage/src/main/java/mage/abilities/common/DealsCombatDamageEquippedTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DealsCombatDamageEquippedTriggeredAbility.java @@ -4,7 +4,7 @@ import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; import mage.constants.Zone; import mage.game.Game; -import mage.game.events.DamagedBatchEvent; +import mage.game.events.DamagedBatchAllEvent; import mage.game.events.DamagedEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; @@ -14,21 +14,17 @@ import mage.game.permanent.Permanent; */ public class DealsCombatDamageEquippedTriggeredAbility extends TriggeredAbilityImpl { - private boolean usedThisStep; - public DealsCombatDamageEquippedTriggeredAbility(Effect effect) { this(effect, false); } public DealsCombatDamageEquippedTriggeredAbility(Effect effect, boolean optional) { super(Zone.BATTLEFIELD, effect, optional); - this.usedThisStep = false; setTriggerPhrase("Whenever equipped creature deals combat damage, "); } protected DealsCombatDamageEquippedTriggeredAbility(final DealsCombatDamageEquippedTriggeredAbility ability) { super(ability); - this.usedThisStep = ability.usedThisStep; } @Override @@ -38,23 +34,16 @@ public class DealsCombatDamageEquippedTriggeredAbility extends TriggeredAbilityI @Override public boolean checkEventType(GameEvent event, Game game) { - return event instanceof DamagedBatchEvent || event.getType() == GameEvent.EventType.COMBAT_DAMAGE_STEP_PRE; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ALL; } @Override public boolean checkTrigger(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.COMBAT_DAMAGE_STEP_PRE) { - usedThisStep = false; // clear before damage - return false; - } - if (usedThisStep || !(event instanceof DamagedBatchEvent)) { - return false; // trigger only on DamagedEvent and if not yet triggered this step - } Permanent sourcePermanent = getSourcePermanentOrLKI(game); if (sourcePermanent == null || sourcePermanent.getAttachedTo() == null) { return false; } - int amount = ((DamagedBatchEvent) event) + int amount = ((DamagedBatchAllEvent) event) .getEvents() .stream() .filter(DamagedEvent::isCombatDamage) @@ -64,11 +53,7 @@ public class DealsCombatDamageEquippedTriggeredAbility extends TriggeredAbilityI if (amount < 1) { return false; } - usedThisStep = true; this.getEffects().setValue("damage", amount); - // TODO: this value saved will not be correct if both permanent and player damaged by a single creature - // Need to rework engine logic to fire a single DamagedBatchEvent including both permanents and players - // Only Sword of Hours is currently affected. return true; } } diff --git a/Mage/src/main/java/mage/abilities/common/DealsCombatDamageTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DealsCombatDamageTriggeredAbility.java index d59c7a6bf50..ea1be8fa952 100644 --- a/Mage/src/main/java/mage/abilities/common/DealsCombatDamageTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DealsCombatDamageTriggeredAbility.java @@ -4,7 +4,7 @@ import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; import mage.constants.Zone; import mage.game.Game; -import mage.game.events.DamagedBatchEvent; +import mage.game.events.DamagedBatchAllEvent; import mage.game.events.DamagedEvent; import mage.game.events.GameEvent; @@ -17,18 +17,14 @@ import mage.game.events.GameEvent; */ public class DealsCombatDamageTriggeredAbility extends TriggeredAbilityImpl { - private boolean usedThisStep; - public DealsCombatDamageTriggeredAbility(Effect effect, boolean optional) { super(Zone.BATTLEFIELD, effect, optional); - this.usedThisStep = false; setTriggerPhrase(getWhen() + "{this} deals combat damage, "); this.replaceRuleText = true; } protected DealsCombatDamageTriggeredAbility(final DealsCombatDamageTriggeredAbility ability) { super(ability); - this.usedThisStep = ability.usedThisStep; } @Override @@ -38,33 +34,22 @@ public class DealsCombatDamageTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event instanceof DamagedBatchEvent || event.getType() == GameEvent.EventType.COMBAT_DAMAGE_STEP_PRE; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ALL; } @Override public boolean checkTrigger(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.COMBAT_DAMAGE_STEP_PRE) { - usedThisStep = false; // clear before damage - return false; - } - if (usedThisStep || !(event instanceof DamagedBatchEvent)) { - return false; // trigger only on DamagedEvent and if not yet triggered this step - } - int amount = ((DamagedBatchEvent) event) + int amount = ((DamagedBatchAllEvent) event) .getEvents() .stream() .filter(DamagedEvent::isCombatDamage) - .filter(e -> e.getAttackerId().equals(this.sourceId)) + .filter(e -> e.getAttackerId().equals(getSourceId())) .mapToInt(GameEvent::getAmount) .sum(); if (amount < 1) { return false; } - usedThisStep = true; this.getEffects().setValue("damage", amount); - // TODO: this value saved will not be correct if both permanent and player damaged by a single creature - // Need to rework engine logic to fire a single DamagedBatchEvent including both permanents and players - // Only Aisha of Sparks and Smoke is currently affected. return true; } } diff --git a/Mage/src/main/java/mage/abilities/common/DealtDamageAttachedTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DealtDamageAttachedTriggeredAbility.java index 7e00d119919..48227613ddd 100644 --- a/Mage/src/main/java/mage/abilities/common/DealtDamageAttachedTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DealtDamageAttachedTriggeredAbility.java @@ -42,7 +42,7 @@ public class DealtDamageAttachedTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT; + return event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT; } @Override diff --git a/Mage/src/main/java/mage/abilities/common/DiesOneOrMoreCreatureTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DiesOneOrMoreCreatureTriggeredAbility.java new file mode 100644 index 00000000000..fad8d0b5eb7 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/DiesOneOrMoreCreatureTriggeredAbility.java @@ -0,0 +1,62 @@ +package mage.abilities.common; + +import mage.MageObject; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.Effect; +import mage.constants.Zone; +import mage.filter.common.FilterCreaturePermanent; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeBatchEvent; +import mage.game.events.ZoneChangeEvent; + +import java.util.Objects; + +/** + * @author Susucr + */ +public class DiesOneOrMoreCreatureTriggeredAbility extends TriggeredAbilityImpl { + + private final FilterCreaturePermanent filter; + + public DiesOneOrMoreCreatureTriggeredAbility(Effect effect, FilterCreaturePermanent filter) { + super(Zone.BATTLEFIELD, effect, false); + this.filter = filter; + this.setTriggerPhrase("Whenever one or more " + filter.getMessage() + " die, "); + } + + private DiesOneOrMoreCreatureTriggeredAbility(final DiesOneOrMoreCreatureTriggeredAbility ability) { + super(ability); + this.filter = ability.filter; + } + + @Override + public DiesOneOrMoreCreatureTriggeredAbility copy() { + return new DiesOneOrMoreCreatureTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ZONE_CHANGE_BATCH; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return ((ZoneChangeBatchEvent) event) + .getEvents() + .stream() + .filter(ZoneChangeEvent::isDiesEvent) + .map(ZoneChangeEvent::getTargetId) + .map(game::getPermanentOrLKIBattlefield) + .filter(Objects::nonNull) + .anyMatch(p -> filter.match(p, getControllerId(), this, game)); + } + + @Override + public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { + return ((ZoneChangeBatchEvent) event) + .getEvents() + .stream() + .allMatch(e -> TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, e, game)); + } +} \ No newline at end of file diff --git a/Mage/src/main/java/mage/abilities/common/DrawNthCardTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DrawNthCardTriggeredAbility.java index 4389e8b1519..12911302c05 100644 --- a/Mage/src/main/java/mage/abilities/common/DrawNthCardTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DrawNthCardTriggeredAbility.java @@ -36,7 +36,9 @@ public class DrawNthCardTriggeredAbility extends TriggeredAbilityImpl { super(zone, effect, optional); this.targetController = targetController; this.cardNumber = cardNumber; - this.addHint(hint); + if (targetController == TargetController.YOU) { + this.addHint(hint); + } setTriggerPhrase(generateTriggerPhrase()); } diff --git a/Mage/src/main/java/mage/abilities/common/EntersBattlefieldTappedAsItEntersChooseColorAbility.java b/Mage/src/main/java/mage/abilities/common/EntersBattlefieldTappedAsItEntersChooseColorAbility.java new file mode 100644 index 00000000000..0b74ebd385d --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/EntersBattlefieldTappedAsItEntersChooseColorAbility.java @@ -0,0 +1,47 @@ + +package mage.abilities.common; + +import mage.abilities.StaticAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.EntersBattlefieldEffect; +import mage.abilities.effects.common.ChooseColorEffect; +import mage.abilities.effects.common.TapSourceEffect; +import mage.constants.Outcome; +import mage.constants.Zone; + +/** + * @author Susucr + */ +public class EntersBattlefieldTappedAsItEntersChooseColorAbility extends StaticAbility { + + public EntersBattlefieldTappedAsItEntersChooseColorAbility() { + super(Zone.ALL, new EntersBattlefieldEffect(new TapSourceEffect(true))); + this.addEffect(new ChooseColorEffect(Outcome.Benefit)); + } + + private EntersBattlefieldTappedAsItEntersChooseColorAbility(final EntersBattlefieldTappedAsItEntersChooseColorAbility ability) { + super(ability); + } + + @Override + public EntersBattlefieldTappedAsItEntersChooseColorAbility copy() { + return new EntersBattlefieldTappedAsItEntersChooseColorAbility(this); + } + + @Override + public void addEffect(Effect effect) { + if (!getEffects().isEmpty()) { + Effect entersEffect = this.getEffects().get(0); + if (entersEffect instanceof EntersBattlefieldEffect) { + ((EntersBattlefieldEffect) entersEffect).addEffect(effect); + return; + } + } + super.addEffect(effect); + } + + @Override + public String getRule() { + return "{this} enters the battlefield tapped. As it enters, choose a color."; + } +} \ No newline at end of file diff --git a/Mage/src/main/java/mage/abilities/common/OneOrMoreDealDamageTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/OneOrMoreDealDamageTriggeredAbility.java index ecfb902c84f..c7cf17da6ed 100644 --- a/Mage/src/main/java/mage/abilities/common/OneOrMoreDealDamageTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/OneOrMoreDealDamageTriggeredAbility.java @@ -6,7 +6,7 @@ import mage.constants.SetTargetPointer; import mage.constants.Zone; import mage.filter.FilterPermanent; import mage.game.Game; -import mage.game.events.DamagedBatchEvent; +import mage.game.events.DamagedBatchForOnePlayerEvent; import mage.game.events.DamagedEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; @@ -60,7 +60,7 @@ public class OneOrMoreDealDamageTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { - DamagedBatchEvent dEvent = (DamagedBatchEvent) event; + DamagedBatchForOnePlayerEvent dEvent = (DamagedBatchForOnePlayerEvent) event; if (onlyCombat && !dEvent.isCombatDamage()) { return false; } @@ -86,7 +86,7 @@ public class OneOrMoreDealDamageTriggeredAbility extends TriggeredAbilityImpl { this.getAllEffects().setValue("damage", events.stream().mapToInt(DamagedEvent::getAmount).sum()); switch (setTargetPointer) { case PLAYER: - this.getAllEffects().setTargetPointer(new FixedTarget(event.getPlayerId())); + this.getAllEffects().setTargetPointer(new FixedTarget(event.getTargetId())); break; case NONE: break; diff --git a/Mage/src/main/java/mage/abilities/common/PayMoreToCastAsThoughtItHadFlashAbility.java b/Mage/src/main/java/mage/abilities/common/PayMoreToCastAsThoughtItHadFlashAbility.java index 6aa1b8909ad..7fc7d21b35c 100644 --- a/Mage/src/main/java/mage/abilities/common/PayMoreToCastAsThoughtItHadFlashAbility.java +++ b/Mage/src/main/java/mage/abilities/common/PayMoreToCastAsThoughtItHadFlashAbility.java @@ -20,7 +20,7 @@ public class PayMoreToCastAsThoughtItHadFlashAbility extends SpellAbility { super(card.getSpellAbility().getManaCosts().copy(), card.getName() + " as though it had flash", Zone.HAND, SpellAbilityType.BASE_ALTERNATE); this.costsToAdd = costsToAdd; this.timing = TimingRule.INSTANT; - this.ruleAtTheTop = true; + this.setRuleAtTheTop(true); CardUtil.increaseCost(this, costsToAdd); } diff --git a/Mage/src/main/java/mage/abilities/common/TapForManaAllTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/TapForManaAllTriggeredAbility.java index 35020205b0d..176507ee5ec 100644 --- a/Mage/src/main/java/mage/abilities/common/TapForManaAllTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/TapForManaAllTriggeredAbility.java @@ -34,7 +34,7 @@ public class TapForManaAllTriggeredAbility extends TriggeredAbilityImpl { setTriggerPhrase("Whenever " + filter.getMessage() + " for mana, "); } - public TapForManaAllTriggeredAbility(TapForManaAllTriggeredAbility ability) { + private TapForManaAllTriggeredAbility(TapForManaAllTriggeredAbility ability) { super(ability); this.filter = ability.filter.copy(); this.setTargetPointer = ability.setTargetPointer; diff --git a/Mage/src/main/java/mage/abilities/common/TapForManaAllTriggeredManaAbility.java b/Mage/src/main/java/mage/abilities/common/TapForManaAllTriggeredManaAbility.java index cd5e4fc6490..151ac062c52 100644 --- a/Mage/src/main/java/mage/abilities/common/TapForManaAllTriggeredManaAbility.java +++ b/Mage/src/main/java/mage/abilities/common/TapForManaAllTriggeredManaAbility.java @@ -28,7 +28,7 @@ public class TapForManaAllTriggeredManaAbility extends TriggeredManaAbility { setTriggerPhrase("Whenever " + filter.getMessage() + " for mana, "); } - public TapForManaAllTriggeredManaAbility(TapForManaAllTriggeredManaAbility ability) { + private TapForManaAllTriggeredManaAbility(TapForManaAllTriggeredManaAbility ability) { super(ability); this.filter = ability.filter.copy(); this.setTargetPointer = ability.setTargetPointer; diff --git a/Mage/src/main/java/mage/abilities/common/TapLandForManaAllTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/TapLandForManaAllTriggeredAbility.java deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/Mage/src/main/java/mage/abilities/common/TapLandForManaAllTriggeredManaAbility.java b/Mage/src/main/java/mage/abilities/common/TapLandForManaAllTriggeredManaAbility.java deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/Mage/src/main/java/mage/abilities/common/delayed/AtTheBeginOfNextCleanupDelayedTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/delayed/AtTheBeginOfNextCleanupDelayedTriggeredAbility.java index dff7c520944..a66ad264f99 100644 --- a/Mage/src/main/java/mage/abilities/common/delayed/AtTheBeginOfNextCleanupDelayedTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/delayed/AtTheBeginOfNextCleanupDelayedTriggeredAbility.java @@ -43,7 +43,7 @@ public class AtTheBeginOfNextCleanupDelayedTriggeredAbility extends DelayedTrigg @Override public String getRule() { StringBuilder sb = new StringBuilder(); - String text = modes.getText(); + String text = getModes().getText(); if (!text.isEmpty()) { sb.append(Character.toUpperCase(text.charAt(0))); if (text.endsWith(".")) { diff --git a/Mage/src/main/java/mage/abilities/common/delayed/AtTheBeginOfNextUpkeepDelayedTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/delayed/AtTheBeginOfNextUpkeepDelayedTriggeredAbility.java index fe63dfbd20c..b462c42ec41 100644 --- a/Mage/src/main/java/mage/abilities/common/delayed/AtTheBeginOfNextUpkeepDelayedTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/delayed/AtTheBeginOfNextUpkeepDelayedTriggeredAbility.java @@ -43,7 +43,7 @@ public class AtTheBeginOfNextUpkeepDelayedTriggeredAbility extends DelayedTrigge @Override public String getRule() { StringBuilder sb = new StringBuilder(); - String text = modes.getText(); + String text = getModes().getText(); if (!text.isEmpty()) { sb.append(Character.toUpperCase(text.charAt(0))); if (text.endsWith(".")) { diff --git a/Mage/src/main/java/mage/abilities/condition/common/CommittedCrimeCondition.java b/Mage/src/main/java/mage/abilities/condition/common/CommittedCrimeCondition.java new file mode 100644 index 00000000000..de62890dc89 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/condition/common/CommittedCrimeCondition.java @@ -0,0 +1,32 @@ +package mage.abilities.condition.common; + +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.abilities.hint.ConditionHint; +import mage.abilities.hint.Hint; +import mage.game.Game; +import mage.watchers.common.CommittedCrimeWatcher; + +/** + * requires CommittedCrimeWatcher + * + * @author TheElk801 + */ +public enum CommittedCrimeCondition implements Condition { + instance; + private static final Hint hint = new ConditionHint(instance, "You committed a crime this turn"); + + public static Hint getHint() { + return hint; + } + + @Override + public boolean apply(Game game, Ability source) { + return CommittedCrimeWatcher.checkCriminality(game, source); + } + + @Override + public String toString() { + return "you've committed a crime this turn"; + } +} diff --git a/Mage/src/main/java/mage/abilities/condition/common/HaventCastSpellFromHandThisTurnCondition.java b/Mage/src/main/java/mage/abilities/condition/common/HaventCastSpellFromHandThisTurnCondition.java new file mode 100644 index 00000000000..c63c3cd4d63 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/condition/common/HaventCastSpellFromHandThisTurnCondition.java @@ -0,0 +1,35 @@ +package mage.abilities.condition.common; + +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.abilities.hint.ConditionHint; +import mage.abilities.hint.Hint; +import mage.constants.Zone; +import mage.game.Game; +import mage.watchers.common.SpellsCastWatcher; + +import java.util.Objects; + +/** + * @author Susucr + */ +public enum HaventCastSpellFromHandThisTurnCondition implements Condition { + instance; + + public static final Hint hint = new ConditionHint(instance, "No spell cast from hand this turn", null, "Have cast spell from hand this turn", null, true); + + @Override + public boolean apply(Game game, Ability source) { + return game.getState() + .getWatcher(SpellsCastWatcher.class) + .getSpellsCastThisTurn(source.getControllerId()) + .stream() + .filter(Objects::nonNull) + .noneMatch(spell -> Zone.HAND.equals(spell.getFromZone())); + } + + @Override + public String toString() { + return "if you haven't cast a spell from your hand this turn"; + } +} diff --git a/Mage/src/main/java/mage/abilities/condition/common/HaventCastSpellThisTurnCondition.java b/Mage/src/main/java/mage/abilities/condition/common/HaventCastSpellThisTurnCondition.java new file mode 100644 index 00000000000..9e60ec2fe20 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/condition/common/HaventCastSpellThisTurnCondition.java @@ -0,0 +1,26 @@ +package mage.abilities.condition.common; + +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.game.Game; +import mage.watchers.common.SpellsCastWatcher; + +/** + * @author Susucr + */ +public enum HaventCastSpellThisTurnCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + return game.getState() + .getWatcher(SpellsCastWatcher.class) + .getSpellsCastThisTurn(source.getControllerId()) + .isEmpty(); + } + + @Override + public String toString() { + return "if you haven't cast a spell this turn"; + } +} diff --git a/Mage/src/main/java/mage/abilities/condition/common/SaddledCondition.java b/Mage/src/main/java/mage/abilities/condition/common/SaddledCondition.java new file mode 100644 index 00000000000..a2c1d4770e6 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/condition/common/SaddledCondition.java @@ -0,0 +1,23 @@ +package mage.abilities.condition.common; + +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.game.Game; +import mage.game.permanent.Permanent; + +import java.util.Optional; + +/** + * @author TheElk801 + */ +public enum SaddledCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + return Optional + .ofNullable(source.getSourcePermanentIfItStillExists(game)) + .map(Permanent::isSaddled) + .orElse(false); + } +} diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/common/CommanderGreatestManaValue.java b/Mage/src/main/java/mage/abilities/dynamicvalue/common/CommanderGreatestManaValue.java new file mode 100644 index 00000000000..b1a6f6dfbfc --- /dev/null +++ b/Mage/src/main/java/mage/abilities/dynamicvalue/common/CommanderGreatestManaValue.java @@ -0,0 +1,41 @@ +package mage.abilities.dynamicvalue.common; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.constants.CommanderCardType; +import mage.constants.Zone; +import mage.game.Game; + +/** + * @author PurpleCrowbar + */ +public enum CommanderGreatestManaValue implements DynamicValue { + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return game.getCommanderCardsFromAnyZones( + game.getPlayer(sourceAbility.getControllerId()), CommanderCardType.ANY, Zone.ALL) + .stream() + .mapToInt(MageObject::getManaValue) + .max() + .orElse(0); + } + + @Override + public CommanderGreatestManaValue copy() { + return this; + } + + @Override + public String toString() { + return "X"; + } + + @Override + public String getMessage() { + return "the greatest mana value among your commanders"; + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/CastSourceTriggeredAbility.java b/Mage/src/main/java/mage/abilities/effects/common/CastSourceTriggeredAbility.java index fa9ef82882e..e8c5406a9a5 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CastSourceTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CastSourceTriggeredAbility.java @@ -21,7 +21,7 @@ public class CastSourceTriggeredAbility extends TriggeredAbilityImpl { public CastSourceTriggeredAbility(Effect effect, boolean optional) { super(Zone.STACK, effect, optional); - this.ruleAtTheTop = true; + this.setRuleAtTheTop(true); setTriggerPhrase("When you cast this spell, "); } diff --git a/Mage/src/main/java/mage/abilities/effects/common/CastTargetForFreeEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CastTargetForFreeEffect.java deleted file mode 100644 index 09183a99989..00000000000 --- a/Mage/src/main/java/mage/abilities/effects/common/CastTargetForFreeEffect.java +++ /dev/null @@ -1,52 +0,0 @@ -package mage.abilities.effects.common; - -import mage.ApprovingObject; -import mage.abilities.Ability; -import mage.abilities.Mode; -import mage.abilities.effects.OneShotEffect; -import mage.cards.Card; -import mage.constants.Outcome; -import mage.game.Game; -import mage.players.Player; - -/** - * @author BetaSteward_at_googlemail.com - */ -public class CastTargetForFreeEffect extends OneShotEffect { - - public CastTargetForFreeEffect() { - super(Outcome.Benefit); - } - - protected CastTargetForFreeEffect(final CastTargetForFreeEffect effect) { - super(effect); - } - - @Override - public CastTargetForFreeEffect copy() { - return new CastTargetForFreeEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - Card target = (Card) game.getObject(source.getFirstTarget()); - if (controller != null && target != null) { - game.getState().setValue("PlayFromNotOwnHandZone" + target.getId(), Boolean.TRUE); - boolean cardWasCast = controller.cast(controller.chooseAbilityForCast(target, game, true), - game, true, new ApprovingObject(source, game)); - game.getState().setValue("PlayFromNotOwnHandZone" + target.getId(), null); - return cardWasCast; - } - return false; - } - - @Override - public String getText(Mode mode) { - if (staticText != null && !staticText.isEmpty()) { - return staticText; - } - return "you may cast " + getTargetPointer().describeTargets(mode.getTargets(), "that card") - + " without paying its mana cost"; - } -} diff --git a/Mage/src/main/java/mage/abilities/effects/common/DamageTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/DamageTargetEffect.java index 4e7aede57ab..ab9a4ce80db 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/DamageTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/DamageTargetEffect.java @@ -24,7 +24,7 @@ public class DamageTargetEffect extends OneShotEffect { protected DynamicValue amount; protected boolean preventable; protected String targetDescription; - protected boolean useOnlyTargetPointer; + protected boolean useOnlyTargetPointer; // why do we ignore targetPointer by default?? protected String sourceName = "{this}"; public DamageTargetEffect(int amount) { @@ -44,6 +44,10 @@ public class DamageTargetEffect extends OneShotEffect { this(StaticValue.get(amount), preventable, targetDescription); } + public DamageTargetEffect(int amount, boolean preventable, String targetDescription, boolean useOnlyTargetPointer) { + this(StaticValue.get(amount), preventable, targetDescription, useOnlyTargetPointer); + } + public DamageTargetEffect(int amount, boolean preventable, String targetDescription, String whoDealDamageName) { this(StaticValue.get(amount), preventable, targetDescription); this.sourceName = whoDealDamageName; diff --git a/Mage/src/main/java/mage/abilities/effects/common/ExileReturnBattlefieldNextEndStepTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ExileReturnBattlefieldNextEndStepTargetEffect.java index 9ea93adf090..8a45e7c21e6 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ExileReturnBattlefieldNextEndStepTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ExileReturnBattlefieldNextEndStepTargetEffect.java @@ -6,14 +6,12 @@ import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbil import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; import mage.cards.Card; -import mage.cards.CardsImpl; import mage.constants.Outcome; import mage.game.Game; import mage.players.Player; import mage.target.targetpointer.FixedTargets; import mage.util.CardUtil; -import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.Objects; import java.util.Set; @@ -75,7 +73,10 @@ public class ExileReturnBattlefieldNextEndStepTargetEffect extends OneShotEffect Effect effect = yourControl ? new ReturnToBattlefieldUnderYourControlTargetEffect(exiledOnly) : new ReturnToBattlefieldUnderOwnerControlTargetEffect(false, exiledOnly); - effect.setTargetPointer(new FixedTargets(toExile, game)); + effect.setTargetPointer(new FixedTargets(toExile + .stream() + .map(Card::getMainCard) + .collect(Collectors.toSet()), game)); game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect), source); return true; } 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 49d882f42b5..95f9e58327a 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ExileSpellWithTimeCountersEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ExileSpellWithTimeCountersEffect.java @@ -1,7 +1,9 @@ package mage.abilities.effects.common; +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.constants.Outcome; @@ -18,16 +20,23 @@ import java.util.UUID; public class ExileSpellWithTimeCountersEffect extends OneShotEffect { private final int counters; + private final boolean gainsSuspend; public ExileSpellWithTimeCountersEffect(int counters) { - super(Outcome.Exile); - this.counters = counters; - this.staticText = "Exile {this} with " + CardUtil.numberToText(this.counters) + " time counters on it"; + this (counters, false); } + public ExileSpellWithTimeCountersEffect(int counters, boolean gainsSuspend) { + super(Outcome.Exile); + this.counters = counters; + this.gainsSuspend = gainsSuspend; + 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; + this.gainsSuspend = effect.gainsSuspend; } @Override @@ -38,14 +47,17 @@ public class ExileSpellWithTimeCountersEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Card card = game.getStack().getSpell(source.getId()).getCard(); + Card card = game.getCard(source.getSourceId()); if (controller == null || card == null) { - return true; + return false; } UUID exileId = SuspendAbility.getSuspendExileId(controller.getId(), game); if (!card.isCopy() && controller.moveCardsToExile(card, source, game, true, exileId, "Suspended cards of " + controller.getName())) { card.addCounters(CounterType.TIME.createInstance(3), source.getControllerId(), source, game); game.informPlayers(controller.getLogName() + " exiles " + card.getLogName() + " with " + counters + " time counters on it"); + if (gainsSuspend) { + game.addEffect(new GainSuspendEffect(new MageObjectReference(card, game)), source); + } } return true; } diff --git a/Mage/src/main/java/mage/abilities/effects/common/LoseHalfLifeTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/LoseHalfLifeTargetEffect.java index 783bd56dfaa..ffe07705f58 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/LoseHalfLifeTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/LoseHalfLifeTargetEffect.java @@ -2,6 +2,7 @@ package mage.abilities.effects.common; import mage.abilities.Ability; +import mage.abilities.Mode; import mage.abilities.effects.OneShotEffect; import mage.constants.Outcome; import mage.game.Game; @@ -14,7 +15,6 @@ public class LoseHalfLifeTargetEffect extends OneShotEffect { public LoseHalfLifeTargetEffect() { super(Outcome.Damage); - staticText = "that player loses half their life, rounded up"; } protected LoseHalfLifeTargetEffect(final LoseHalfLifeTargetEffect effect) { @@ -30,7 +30,7 @@ public class LoseHalfLifeTargetEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { - Integer amount = (int) Math.ceil(player.getLife() / 2f); + int amount = (int) Math.ceil(player.getLife() / 2f); if (amount > 0) { player.loseLife(amount, game, source, false); return true; @@ -38,4 +38,12 @@ public class LoseHalfLifeTargetEffect extends OneShotEffect { } return false; } + + @Override + public String getText(Mode mode) { + if (staticText != null && !staticText.isEmpty()) { + return staticText; + } + return getTargetPointer().describeTargets(mode.getTargets(), "that player") + " loses half their life, rounded up"; + } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/MayCastTargetCardEffect.java b/Mage/src/main/java/mage/abilities/effects/common/MayCastTargetCardEffect.java new file mode 100644 index 00000000000..4d8d0cee936 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/MayCastTargetCardEffect.java @@ -0,0 +1,166 @@ +package mage.abilities.effects.common; + +import mage.ApprovingObject; +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.asthought.YouMaySpendManaAsAnyColorToCastTargetEffect; +import mage.abilities.effects.common.replacement.ThatSpellGraveyardExileReplacementEffect; +import mage.cards.Card; +import mage.constants.CastManaAdjustment; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.game.Game; +import mage.players.Player; +import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; + +/** + * @author xenohedron, Susucr + */ +public class MayCastTargetCardEffect extends OneShotEffect { + + private final Duration duration; + + private final CastManaAdjustment manaAdjustment; + + private final boolean thenExile; // Should the spell be exiled by a replacement effect if cast and it resolves? + + /** + * Allows to cast the target card immediately, for its manacost. + */ + public MayCastTargetCardEffect(boolean thenExile) { + this(CastManaAdjustment.NONE, thenExile); + } + + /** + * Allows to cast the target card immediately, either for its cost or with a modifier (like for free, or mana as any type). + */ + public MayCastTargetCardEffect(CastManaAdjustment manaAdjustment) { + this(manaAdjustment, false); + } + + /** + * Allows to cast the target card immediately, either for its cost or with a modifier (like for free, or mana as any type). + */ + public MayCastTargetCardEffect(CastManaAdjustment manaAdjustment, boolean thenExile) { + this(Duration.OneUse, manaAdjustment, thenExile); + } + + /** + * Makes the target card playable for the specified duration as long as it remains in that zone. + */ + public MayCastTargetCardEffect(Duration duration, boolean thenExile) { + this(duration, CastManaAdjustment.NONE, thenExile); + } + + protected MayCastTargetCardEffect(Duration duration, CastManaAdjustment manaAdjustment, boolean thenExile) { + super(Outcome.Benefit); + this.duration = duration; + this.manaAdjustment = manaAdjustment; + this.thenExile = thenExile; + + // TODO: support the non-yet-supported combinations. + // for now the constructor chains won't allow those. + if (duration != Duration.OneUse && manaAdjustment != CastManaAdjustment.NONE) { + throw new IllegalStateException( + "Wrong code usage, not yet supported " + + "duration={" + duration.name() + "}, " + + "manaAdjustment={" + manaAdjustment.name() + "}" + ); + } + } + + protected MayCastTargetCardEffect(final MayCastTargetCardEffect effect) { + super(effect); + this.duration = effect.duration; + this.manaAdjustment = effect.manaAdjustment; + this.thenExile = effect.thenExile; + } + + @Override + public MayCastTargetCardEffect copy() { + return new MayCastTargetCardEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Card card = game.getCard(getTargetPointer().getFirst(game, source)); + if (card == null) { + return false; + } + FixedTarget fixedTarget = new FixedTarget(card, game); + if (duration == Duration.OneUse) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null || !controller.chooseUse(outcome, "Cast " + card.getLogName() + '?', source, game)) { + return false; + } + + switch (manaAdjustment) { + case NONE: + case WITHOUT_PAYING_MANA_COST: + break; + case AS_THOUGH_ANY_MANA_COLOR: + case AS_THOUGH_ANY_MANA_TYPE: + // TODO: untangle why there is a confusion between the two. + ContinuousEffect effect = + new YouMaySpendManaAsAnyColorToCastTargetEffect(Duration.Custom, controller.getId(), null); + effect.setTargetPointer(fixedTarget.copy()); + game.addEffect(effect, source); + break; + default: + throw new IllegalArgumentException("Wrong code usage, manaAdjustment is not yet supported: " + manaAdjustment); + } + + game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE); + boolean noMana = manaAdjustment == CastManaAdjustment.WITHOUT_PAYING_MANA_COST; + controller.cast(controller.chooseAbilityForCast(card, game, noMana), + game, noMana, new ApprovingObject(source, game)); + game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null); + } else { + // TODO: support (and add tests!) for the non-NONE manaAdjustment + CardUtil.makeCardPlayable(game, source, card, duration, false); + } + if (thenExile) { + ContinuousEffect effect = new ThatSpellGraveyardExileReplacementEffect(true); + effect.setTargetPointer(fixedTarget.copy()); + game.addEffect(effect, source); + } + return true; + } + + @Override + public String getText(Mode mode) { + if (staticText != null && !staticText.isEmpty()) { + return staticText; + } + String text = "you may cast " + getTargetPointer().describeTargets(mode.getTargets(), "it"); + if (duration == Duration.EndOfTurn) { + text += " this turn"; + } else if (!duration.toString().isEmpty()) { + text += duration.toString(); + } + switch (manaAdjustment) { + case NONE: + break; + case WITHOUT_PAYING_MANA_COST: + text += " without paying its mana cost"; + break; + case AS_THOUGH_ANY_MANA_COLOR: + text += ", and mana of any color can be spent to cast that spell"; + break; + case AS_THOUGH_ANY_MANA_TYPE: + text += ", and mana of any type can be spent to cast that spell"; + break; + default: + throw new IllegalArgumentException("Wrong code usage, manaAdjustment is not yet supported: " + manaAdjustment); + } + text += "."; + if (thenExile) { + text += " " + ThatSpellGraveyardExileReplacementEffect.RULE_YOUR; + } + return text; + } + +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/MayCastTargetThenExileEffect.java b/Mage/src/main/java/mage/abilities/effects/common/MayCastTargetThenExileEffect.java deleted file mode 100644 index cd65e9d8a60..00000000000 --- a/Mage/src/main/java/mage/abilities/effects/common/MayCastTargetThenExileEffect.java +++ /dev/null @@ -1,98 +0,0 @@ -package mage.abilities.effects.common; - -import mage.ApprovingObject; -import mage.abilities.Ability; -import mage.abilities.Mode; -import mage.abilities.effects.ContinuousEffect; -import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.replacement.ThatSpellGraveyardExileReplacementEffect; -import mage.cards.Card; -import mage.constants.Duration; -import mage.constants.Outcome; -import mage.game.Game; -import mage.players.Player; -import mage.target.targetpointer.FixedTarget; -import mage.util.CardUtil; - -/** - * @author xenohedron - */ -public class MayCastTargetThenExileEffect extends OneShotEffect { - - private final Duration duration; - private final boolean noMana; - - /** - * Allows to cast the target card immediately, either for its cost or for free. - * If resulting spell would be put into graveyard, exiles it instead. - */ - public MayCastTargetThenExileEffect(boolean noMana) { - super(Outcome.Benefit); - this.duration = Duration.OneUse; - this.noMana = noMana; - } - - /** - * Makes the target card playable for the specified duration as long as it remains in that zone. - * If resulting spell would be put into graveyard, exiles it instead. - */ - public MayCastTargetThenExileEffect(Duration duration) { - super(Outcome.Benefit); - this.duration = duration; - this.noMana = false; - } - - protected MayCastTargetThenExileEffect(final MayCastTargetThenExileEffect effect) { - super(effect); - this.duration = effect.duration; - this.noMana = effect.noMana; - } - - @Override - public MayCastTargetThenExileEffect copy() { - return new MayCastTargetThenExileEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Card card = game.getCard(getTargetPointer().getFirst(game, source)); - if (card == null) { - return false; - } - FixedTarget fixedTarget = new FixedTarget(card, game); - if (duration == Duration.OneUse) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller == null || !controller.chooseUse(outcome, "Cast " + card.getLogName() + '?', source, game)) { - return false; - } - game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE); - controller.cast(controller.chooseAbilityForCast(card, game, noMana), - game, noMana, new ApprovingObject(source, game)); - game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null); - } else { - CardUtil.makeCardPlayable(game, source, card, duration, false); - } - ContinuousEffect effect = new ThatSpellGraveyardExileReplacementEffect(true); - effect.setTargetPointer(fixedTarget); - game.addEffect(effect, source); - return true; - } - - @Override - public String getText(Mode mode) { - if (staticText != null && !staticText.isEmpty()) { - return staticText; - } - String text = "you may cast " + getTargetPointer().describeTargets(mode.getTargets(), "it"); - if (duration == Duration.EndOfTurn) { - text += " this turn"; - } else if (!duration.toString().isEmpty()) { - text += duration.toString(); - } - if (noMana) { - text += " without paying its mana cost"; - } - return text + ". " + ThatSpellGraveyardExileReplacementEffect.RULE_YOUR; - } - -} diff --git a/Mage/src/main/java/mage/abilities/effects/common/MayExileCardFromHandPlottedEffect.java b/Mage/src/main/java/mage/abilities/effects/common/MayExileCardFromHandPlottedEffect.java new file mode 100644 index 00000000000..1f0a1c2546b --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/MayExileCardFromHandPlottedEffect.java @@ -0,0 +1,48 @@ +package mage.abilities.effects.common; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.PlotAbility; +import mage.cards.Card; +import mage.constants.Outcome; +import mage.filter.FilterCard; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCardInHand; + +public class MayExileCardFromHandPlottedEffect extends OneShotEffect { + + private final FilterCard filter; + + public MayExileCardFromHandPlottedEffect(FilterCard filter) { + super(Outcome.PutCardInPlay); + this.filter = filter; + this.staticText = "you may exile a " + filter.getMessage() + " from your hand. If you do, it becomes plotted"; + } + + private MayExileCardFromHandPlottedEffect(final MayExileCardFromHandPlottedEffect effect) { + super(effect); + this.filter = effect.filter; + } + + @Override + public MayExileCardFromHandPlottedEffect copy() { + return new MayExileCardFromHandPlottedEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + TargetCardInHand target = new TargetCardInHand(0, 1, filter); + if (player.chooseTarget(outcome, target, source, game)) { + Card card = game.getCard(target.getFirstTarget()); + if (card != null) { + PlotAbility.doExileAndPlotCard(card, game, source); + } + } + return true; + } +} \ No newline at end of file diff --git a/Mage/src/main/java/mage/abilities/effects/common/MillThenPutInHandEffect.java b/Mage/src/main/java/mage/abilities/effects/common/MillThenPutInHandEffect.java index ad8baabac05..343b77ea07b 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/MillThenPutInHandEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/MillThenPutInHandEffect.java @@ -15,6 +15,10 @@ import mage.players.Player; import mage.target.TargetCard; import mage.util.CardUtil; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + /** * @author TheElk801 */ @@ -22,6 +26,7 @@ public class MillThenPutInHandEffect extends OneShotEffect { private final int amount; private final boolean optional; + private final int maxAmountReturned; // maximum number of cards returned. e.g. 2 for "up to two" private final FilterCard filter; private final Effect otherwiseEffect; private String textFromAmong = "the milled cards"; // for text gen @@ -35,8 +40,8 @@ public class MillThenPutInHandEffect extends OneShotEffect { } /** - * @param amount number of cards to mill - * @param filter select a card matching this filter from among the milled cards to put in hand + * @param amount number of cards to mill + * @param filter select a card matching this filter from among the milled cards to put in hand * @param optional whether the selection is optional (true) or mandatory (false) */ public MillThenPutInHandEffect(int amount, FilterCard filter, boolean optional) { @@ -44,8 +49,8 @@ public class MillThenPutInHandEffect extends OneShotEffect { } /** - * @param amount number of cards to mill - * @param filter optionally select a card matching this filter from among the milled cards to put in hand + * @param amount number of cards to mill + * @param filter optionally select a card matching this filter from among the milled cards to put in hand * @param otherwiseEffect applied if no card put into hand */ public MillThenPutInHandEffect(int amount, FilterCard filter, Effect otherwiseEffect) { @@ -53,11 +58,16 @@ public class MillThenPutInHandEffect extends OneShotEffect { this.textFromAmong = "the cards milled this way"; } - protected MillThenPutInHandEffect(int amount, FilterCard filter, Effect otherwiseEffect, boolean optional) { + public MillThenPutInHandEffect(int amount, FilterCard filter, Effect otherwiseEffect, boolean optional) { + this(amount, filter, otherwiseEffect, optional, 1); + } + + public MillThenPutInHandEffect(int amount, FilterCard filter, Effect otherwiseEffect, boolean optional, int maxReturnedCard) { super(Outcome.Benefit); this.amount = amount; this.filter = filter; this.optional = optional; + this.maxAmountReturned = maxReturnedCard; this.otherwiseEffect = otherwiseEffect; } @@ -66,6 +76,7 @@ public class MillThenPutInHandEffect extends OneShotEffect { this.amount = effect.amount; this.optional = effect.optional; this.filter = effect.filter; + this.maxAmountReturned = effect.maxAmountReturned; this.otherwiseEffect = effect.otherwiseEffect; this.textFromAmong = effect.textFromAmong; } @@ -85,13 +96,18 @@ public class MillThenPutInHandEffect extends OneShotEffect { if (cards.isEmpty()) { return applyOtherwiseEffect(game, source); } - TargetCard target = new TargetCard(optional ? 0 : 1, 1, Zone.ALL, filter); + TargetCard target = new TargetCard(optional ? 0 : maxAmountReturned, maxAmountReturned, Zone.ALL, filter); player.choose(Outcome.DrawCard, cards, target, source, game); - Card card = game.getCard(target.getFirstTarget()); - if (card == null) { + Set returned = target + .getTargets() + .stream() + .map(game::getCard) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + if (returned.isEmpty()) { return applyOtherwiseEffect(game, source); } - return player.moveCards(card, Zone.HAND, source, game); + return player.moveCards(returned, Zone.HAND, source, game); } private boolean applyOtherwiseEffect(Game game, Ability source) { @@ -115,12 +131,25 @@ public class MillThenPutInHandEffect extends OneShotEffect { if (staticText != null && !staticText.isEmpty()) { return staticText; } - String text = "mill " + CardUtil.numberToText(amount) + " cards. "; - text += optional ? "You may " : "Then "; - text += "put " + filter.getMessage() + " from among " + textFromAmong + " into your hand"; - if (otherwiseEffect != null) { - text += ". If you don't, " + otherwiseEffect.getText(mode); + StringBuilder sb = new StringBuilder("mill "); + sb.append(CardUtil.numberToText(amount)); + sb.append(" cards. "); + sb.append(optional ? "You may " : "Then "); + sb.append("put "); + if (maxAmountReturned > 1) { + sb.append(optional ? "up to " : ""); + sb.append(CardUtil.numberToText(maxAmountReturned) + " "); } - return text; + sb.append(filter.getMessage()); + sb.append(" from among "); + sb.append(textFromAmong); + sb.append(" into your hand"); + if (otherwiseEffect != null) { + sb.append(". If you "); + sb.append(optional ? "don't" : "can't"); + sb.append(", "); + sb.append(otherwiseEffect.getText(mode)); + } + return sb.toString(); } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/ReturnToBattlefieldUnderOwnerControlTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ReturnToBattlefieldUnderOwnerControlTargetEffect.java index ea31739eff1..0a6ea1f6b1e 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ReturnToBattlefieldUnderOwnerControlTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ReturnToBattlefieldUnderOwnerControlTargetEffect.java @@ -18,8 +18,8 @@ import java.util.UUID; */ public class ReturnToBattlefieldUnderOwnerControlTargetEffect extends OneShotEffect { - private boolean tapped; - protected boolean returnFromExileZoneOnly; + private final boolean tapped; + private final boolean returnFromExileZoneOnly; /** * @param returnFromExileZoneOnly see https://github.com/magefree/mage/issues/5151 @@ -27,14 +27,10 @@ public class ReturnToBattlefieldUnderOwnerControlTargetEffect extends OneShotEff * return exiled card - true */ public ReturnToBattlefieldUnderOwnerControlTargetEffect(boolean tapped, boolean returnFromExileZoneOnly) { - this(tapped, returnFromExileZoneOnly, "that card"); - } - - public ReturnToBattlefieldUnderOwnerControlTargetEffect(boolean tapped, boolean returnFromExileZoneOnly, String description) { super(Outcome.Benefit); this.tapped = tapped; this.returnFromExileZoneOnly = returnFromExileZoneOnly; - staticText = "return " + description + " to the battlefield " + (tapped ? "tapped " : "") + "under its owner's control"; + staticText = "return that card to the battlefield " + (tapped ? "tapped " : "") + "under its owner's control"; } protected ReturnToBattlefieldUnderOwnerControlTargetEffect(final ReturnToBattlefieldUnderOwnerControlTargetEffect effect) { diff --git a/Mage/src/main/java/mage/abilities/effects/common/asthought/YouMaySpendManaAsAnyColorToCastTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/asthought/YouMaySpendManaAsAnyColorToCastTargetEffect.java index b79eee13fcc..ddeb2dbc71a 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/asthought/YouMaySpendManaAsAnyColorToCastTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/asthought/YouMaySpendManaAsAnyColorToCastTargetEffect.java @@ -16,6 +16,7 @@ import java.util.UUID; /** * Spend mana as any color to cast targeted card. Will not affected after any card movements or blinks. * Affects to all card's parts + * TODO: AnyType and AnyColor are confused there. * * @author JayDi85 */ diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/AddCardSubtypeAllEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/AddCardSubtypeAllEffect.java index f725618209d..526075f937a 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/AddCardSubtypeAllEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/AddCardSubtypeAllEffect.java @@ -15,10 +15,14 @@ public class AddCardSubtypeAllEffect extends ContinuousEffectImpl { private final FilterPermanent filter; private final SubType addedSubtype; + /** + * Note: must set text manually + */ public AddCardSubtypeAllEffect(FilterPermanent filter, SubType addedSubtype, DependencyType dependency) { super(Duration.WhileOnBattlefield, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Benefit); this.filter = filter; this.addedSubtype = addedSubtype; + this.staticText = filter.getMessage() + " are " + addedSubtype.getPluralName() + " in addition to their other types"; addDependencyType(dependency); } diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesFaceDownCreatureEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesFaceDownCreatureEffect.java index 472bd01489e..9cdc4c220f9 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesFaceDownCreatureEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesFaceDownCreatureEffect.java @@ -7,6 +7,7 @@ import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.TurnFaceUpAbility; import mage.abilities.costs.Cost; +import mage.abilities.costs.CostAdjuster; import mage.abilities.costs.Costs; import mage.abilities.costs.CostsImpl; import mage.abilities.costs.mana.ManaCostsImpl; @@ -83,7 +84,22 @@ public class BecomesFaceDownCreatureEffect extends ContinuousEffectImpl { this(createCosts(cost), objectReference, duration, faceDownType); } - public BecomesFaceDownCreatureEffect(Costs turnFaceUpCosts, MageObjectReference objectReference, Duration duration, FaceDownType faceDownType) { + public BecomesFaceDownCreatureEffect(Costs cost, MageObjectReference objectReference, Duration duration, FaceDownType faceDownType) { + this(createCosts(cost), objectReference, duration, faceDownType, null); + } + + public BecomesFaceDownCreatureEffect(Cost cost, MageObjectReference objectReference, Duration duration, FaceDownType faceDownType, CostAdjuster costAdjuster) { + this(createCosts(cost), objectReference, duration, faceDownType, costAdjuster); + } + + /** + * @param turnFaceUpCosts costs for the turn face up ability + * @param objectReference + * @param duration + * @param faceDownType type of face down (morph, disguise, manifest, etc...) + * @param costAdjuster optional costAdjuster for the turn face up ability + */ + public BecomesFaceDownCreatureEffect(Costs turnFaceUpCosts, MageObjectReference objectReference, Duration duration, FaceDownType faceDownType, CostAdjuster costAdjuster) { super(duration, Layer.CopyEffects_1, SubLayer.FaceDownEffects_1b, Outcome.BecomeCreature); this.objectReference = objectReference; this.zoneChangeCounter = Integer.MIN_VALUE; @@ -91,7 +107,10 @@ public class BecomesFaceDownCreatureEffect extends ContinuousEffectImpl { // add additional face up and information abilities if (turnFaceUpCosts != null) { // face up for all - this.additionalAbilities.add(new TurnFaceUpAbility(turnFaceUpCosts, faceDownType == FaceDownType.MEGAMORPHED)); + this.additionalAbilities.add( + new TurnFaceUpAbility(turnFaceUpCosts, faceDownType == FaceDownType.MEGAMORPHED) + .setCostAdjuster(costAdjuster) + ); switch (faceDownType) { case MORPHED: diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/LookAtTopCardOfLibraryAnyTimeEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/LookAtTopCardOfLibraryAnyTimeEffect.java index 4cb4694c505..31889f8efe7 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/LookAtTopCardOfLibraryAnyTimeEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/LookAtTopCardOfLibraryAnyTimeEffect.java @@ -1,63 +1,32 @@ package mage.abilities.effects.common.continuous; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.effects.ContinuousEffectImpl; import mage.cards.Card; -import mage.constants.*; +import mage.constants.Duration; +import mage.constants.Layer; +import mage.constants.Outcome; +import mage.constants.SubLayer; import mage.game.Game; import mage.players.Player; -import mage.util.CardUtil; - -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; /** * @author TheElk801 */ public class LookAtTopCardOfLibraryAnyTimeEffect extends ContinuousEffectImpl { - private final TargetController targetLibrary; - public LookAtTopCardOfLibraryAnyTimeEffect() { - this(TargetController.YOU, Duration.WhileOnBattlefield); + this(Duration.WhileOnBattlefield); } - public LookAtTopCardOfLibraryAnyTimeEffect(TargetController targetLibrary, Duration duration) { + public LookAtTopCardOfLibraryAnyTimeEffect(Duration duration) { super(duration, Layer.PlayerEffects, SubLayer.NA, Outcome.Benefit); - this.targetLibrary = targetLibrary; - - String libInfo; - switch (this.targetLibrary) { - case YOU: - libInfo = "your library"; - break; - case OPPONENT: - libInfo = "opponents libraries"; - break; - case SOURCE_TARGETS: - libInfo = "target player's library"; - break; - default: - throw new IllegalArgumentException("Unknown target library type: " + targetLibrary); - } - StringBuilder sb = new StringBuilder(); - String durationString = duration.toString(); - if (durationString != null && !durationString.isEmpty()) { - sb.append(durationString); - sb.append(", "); - } - sb.append("you may look at the top card of "); - sb.append(libInfo); - sb.append(" any time"); - staticText = sb.toString(); + staticText = (duration.toString().isEmpty() ? "" : duration.toString() + ", ") + + "you may look at the top card of your library any time"; } protected LookAtTopCardOfLibraryAnyTimeEffect(final LookAtTopCardOfLibraryAnyTimeEffect effect) { super(effect); - this.targetLibrary = effect.targetLibrary; } @Override @@ -72,43 +41,11 @@ public class LookAtTopCardOfLibraryAnyTimeEffect extends ContinuousEffectImpl { if (!canLookAtNextTopLibraryCard(game)) { return false; } - MageObject obj = source.getSourceObject(game); - if (obj == null) { + Card topCard = controller.getLibrary().getFromTop(game); + if (topCard == null) { return false; } - - Set needPlayers = new HashSet<>(); - switch (this.targetLibrary) { - case YOU: { - needPlayers.add(source.getControllerId()); - break; - } - case OPPONENT: { - needPlayers.addAll(game.getOpponents(source.getControllerId())); - break; - } - case SOURCE_TARGETS: { - needPlayers.addAll(CardUtil.getAllSelectedTargets(source, game)); - break; - } - } - - Set needCards = new HashSet<>(); - needPlayers.stream() - .map(game::getPlayer) - .filter(Objects::nonNull) - .map(player -> player.getLibrary().getFromTop(game)) - .filter(Objects::nonNull) - .forEach(needCards::add); - if (needCards.isEmpty()) { - return false; - } - - // all fine, can show top card - needCards.forEach(topCard -> { - Player owner = game.getPlayer(topCard.getOwnerId()); - controller.lookAtCards(String.format("%s: top card of %s", obj.getName(), owner == null ? "error" : owner.getName()), topCard, game); - }); + controller.lookAtCards("Top card of your library", topCard, game); return true; } diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/LookAtTopCardOfLibraryAnyTimeTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/LookAtTopCardOfLibraryAnyTimeTargetEffect.java deleted file mode 100644 index 4af3cf4d5d7..00000000000 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/LookAtTopCardOfLibraryAnyTimeTargetEffect.java +++ /dev/null @@ -1,23 +0,0 @@ -package mage.abilities.effects.common.continuous; - -import mage.constants.Duration; -import mage.constants.TargetController; - -/** - * @author JayDi85 - */ -public class LookAtTopCardOfLibraryAnyTimeTargetEffect extends LookAtTopCardOfLibraryAnyTimeEffect { - - public LookAtTopCardOfLibraryAnyTimeTargetEffect(Duration duration) { - super(TargetController.SOURCE_TARGETS, duration); - } - - private LookAtTopCardOfLibraryAnyTimeTargetEffect(final LookAtTopCardOfLibraryAnyTimeTargetEffect effect) { - super(effect); - } - - @Override - public LookAtTopCardOfLibraryAnyTimeTargetEffect copy() { - return new LookAtTopCardOfLibraryAnyTimeTargetEffect(this); - } -} diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/NextSpellCastHasAbilityEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/NextSpellCastHasAbilityEffect.java index 8908bef6d6b..88f02aaff83 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/NextSpellCastHasAbilityEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/NextSpellCastHasAbilityEffect.java @@ -13,6 +13,8 @@ import mage.players.Player; import mage.util.CardUtil; import mage.watchers.common.SpellsCastWatcher; +import java.util.UUID; + /** * @author xenohedron */ @@ -21,17 +23,24 @@ public class NextSpellCastHasAbilityEffect extends ContinuousEffectImpl { private int spellsCast; private final Ability ability; private final FilterCard filter; + private final TargetController targetController; public NextSpellCastHasAbilityEffect(Ability ability) { this(ability, StaticFilters.FILTER_CARD); } public NextSpellCastHasAbilityEffect(Ability ability, FilterCard filter) { + this(ability, filter, TargetController.SOURCE_CONTROLLER); + } + + public NextSpellCastHasAbilityEffect(Ability ability, FilterCard filter, TargetController targetController) { super(Duration.EndOfTurn, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); this.ability = ability; this.filter = filter; + this.targetController = targetController; staticText = "the next " + filter.getMessage().replace("card", "spell") - + " you cast this turn has " + CardUtil.getTextWithFirstCharLowerCase(CardUtil.stripReminderText(ability.getRule())); + + (targetController == TargetController.SOURCE_CONTROLLER ? " you cast" : " target player casts") + + " this turn has " + CardUtil.getTextWithFirstCharLowerCase(CardUtil.stripReminderText(ability.getRule())); } private NextSpellCastHasAbilityEffect(final NextSpellCastHasAbilityEffect effect) { @@ -39,6 +48,7 @@ public class NextSpellCastHasAbilityEffect extends ContinuousEffectImpl { this.spellsCast = effect.spellsCast; this.ability = effect.ability; this.filter = effect.filter; + this.targetController = effect.targetController; } @Override @@ -57,17 +67,28 @@ public class NextSpellCastHasAbilityEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); + UUID playerId; + switch (targetController){ + case SOURCE_TARGETS: + playerId = source.getFirstTarget(); + break; + case SOURCE_CONTROLLER: + playerId = source.getControllerId(); + break; + default: + throw new UnsupportedOperationException("Value for targetController in NextSpellCastHasAbilityEffect not supported: " + targetController); + } + Player player = game.getPlayer(playerId); SpellsCastWatcher watcher = game.getState().getWatcher(SpellsCastWatcher.class); if (player == null || watcher == null) { return false; } //check if a spell was cast before - if (watcher.getCount(source.getControllerId()) > spellsCast) { + if (watcher.getCount(playerId) > spellsCast) { discard(); // only one use return false; } - for (Card card : game.getExile().getAllCardsByRange(game, source.getControllerId())) { + for (Card card : game.getExile().getAllCardsByRange(game, playerId)) { if (filter.match(card, game)) { game.getState().addOtherAbility(card, ability); } @@ -94,7 +115,7 @@ public class NextSpellCastHasAbilityEffect extends ContinuousEffectImpl { .forEach(card -> game.getState().addOtherAbility(card, ability)); for (StackObject stackObject : game.getStack()) { - if (!(stackObject instanceof Spell) || !stackObject.isControlledBy(source.getControllerId())) { + if (!(stackObject instanceof Spell) || !stackObject.isControlledBy(playerId)) { continue; } // TODO: Distinguish "you cast" to exclude copies diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/PlayFromTopOfLibraryEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/PlayFromTopOfLibraryEffect.java new file mode 100644 index 00000000000..e1c58df4cbc --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/PlayFromTopOfLibraryEffect.java @@ -0,0 +1,99 @@ +package mage.abilities.effects.common.continuous; + +import mage.abilities.Ability; +import mage.abilities.SpellAbility; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.cards.Card; +import mage.constants.AsThoughEffectType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.filter.FilterCard; +import mage.game.Game; +import mage.players.Player; + +import java.util.Locale; +import java.util.UUID; + +/** + * @author nantuko, JayDi85, xenohedron + */ +public class PlayFromTopOfLibraryEffect extends AsThoughEffectImpl { + + private final FilterCard filter; + + private static final FilterCard defaultFilter = new FilterCard("play lands and cast spells"); + + /** + * You may play lands and cast spells from the top of your library + */ + public PlayFromTopOfLibraryEffect() { + this(defaultFilter); + } + + /** + * You may [play lands and/or cast spells, according to filter] from the top of your library + */ + public PlayFromTopOfLibraryEffect(FilterCard filter) { + super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.Benefit); + this.filter = filter; + this.staticText = "you may " + filter.getMessage() + " from the top of your library"; + + // verify check: this ability is to allow playing lands or casting spells, not playing a "card" + if (filter.getMessage().toLowerCase(Locale.ENGLISH).contains("card")) { + throw new IllegalArgumentException("Wrong code usage or wrong filter text: PlayTheTopCardEffect"); + } + } + + protected PlayFromTopOfLibraryEffect(final PlayFromTopOfLibraryEffect effect) { + super(effect); + this.filter = effect.filter; + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public PlayFromTopOfLibraryEffect copy() { + return new PlayFromTopOfLibraryEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + throw new IllegalArgumentException("Wrong code usage: can't call applies method on empty affectedAbility"); + } + + @Override + public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) { + // can play lands/spells (must check specific part and allows specific part) + + Card cardToCheck = game.getCard(objectId); // maybe this should be removed and only check SpellAbility characteristics + if (cardToCheck == null) { + return false; + } + if (affectedAbility instanceof SpellAbility) { + SpellAbility spell = (SpellAbility) affectedAbility; + cardToCheck = spell.getCharacteristics(game); + if (spell.getManaCosts().isEmpty()){ + return false; + } + } + // only permits you to cast + if (!playerId.equals(source.getControllerId())) { + return false; + } + Player cardOwner = game.getPlayer(cardToCheck.getOwnerId()); + Player controller = game.getPlayer(source.getControllerId()); + if (cardOwner == null || controller == null) { + return false; + } + // main card of spell must be on top of your library + Card topCard = controller.getLibrary().getFromTop(game); + if (topCard == null || !topCard.getId().equals(cardToCheck.getMainCard().getId())) { + return false; + } + // spell characteristics must match filter + return filter.match(cardToCheck, playerId, source, game); + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/PlayTheTopCardEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/PlayTheTopCardEffect.java deleted file mode 100644 index 3eb76dd81b7..00000000000 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/PlayTheTopCardEffect.java +++ /dev/null @@ -1,170 +0,0 @@ -package mage.abilities.effects.common.continuous; - -import mage.abilities.Ability; -import mage.abilities.SpellAbility; -import mage.abilities.effects.AsThoughEffectImpl; -import mage.cards.Card; -import mage.constants.AsThoughEffectType; -import mage.constants.Duration; -import mage.constants.Outcome; -import mage.constants.TargetController; -import mage.filter.FilterCard; -import mage.game.Game; -import mage.players.Player; -import mage.util.CardUtil; - -import java.util.Locale; -import java.util.Objects; -import java.util.UUID; - -/** - * @author nantuko, JayDi85 - */ -public class PlayTheTopCardEffect extends AsThoughEffectImpl { - - private final FilterCard filter; - private final TargetController targetLibrary; - - // can play card or can play lands/cast spells, see two modes below - private final boolean canPlayCardOnly; - - - /** - * Support targets, use TargetController.SOURCE_TARGETS - */ - public PlayTheTopCardEffect() { - this(TargetController.YOU); - } - - public PlayTheTopCardEffect(TargetController targetLibrary) { - this(targetLibrary, new FilterCard("play lands and cast spells"), false); - } - - public PlayTheTopCardEffect(TargetController targetLibrary, FilterCard filter, boolean canPlayCardOnly) { - super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.Benefit); - this.filter = filter; - this.targetLibrary = targetLibrary; - this.canPlayCardOnly = canPlayCardOnly; - - String libInfo; - switch (this.targetLibrary) { - case YOU: - libInfo = "your library"; - break; - case OPPONENT: - libInfo = "opponents libraries"; - break; - case SOURCE_TARGETS: - libInfo = "target player's library"; - break; - default: - throw new IllegalArgumentException("Unknown target library type: " + targetLibrary); - } - this.staticText = "you may " + filter.getMessage() + " from the top of " + libInfo; - - // verify check: if you see "card" text in the rules then use card mode - // (there aren't any real cards after oracle update, but can be added in the future) - if (this.canPlayCardOnly != filter.getMessage().toLowerCase(Locale.ENGLISH).contains("card")) { - throw new IllegalArgumentException("Wrong usage of card mode settings"); - } - } - - protected PlayTheTopCardEffect(final PlayTheTopCardEffect effect) { - super(effect); - this.filter = effect.filter; - this.targetLibrary = effect.targetLibrary; - this.canPlayCardOnly = effect.canPlayCardOnly; - } - - @Override - public boolean apply(Game game, Ability source) { - return true; - } - - @Override - public PlayTheTopCardEffect copy() { - return new PlayTheTopCardEffect(this); - } - - @Override - public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { - throw new IllegalArgumentException("Wrong code usage: can't call applies method on empty affectedAbility"); - } - @Override - public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) { - // main card and all parts are checks in different calls. - // two modes: - // * can play cards (must check main card and allows any parts) - // * can play lands/spells (must check specific part and allows specific part) - - // current card's part - Card cardToCheck = game.getCard(objectId); - if (cardToCheck == null) { - return false; - } - - if (this.canPlayCardOnly) { - // check whole card instead part - cardToCheck = cardToCheck.getMainCard(); - } else if (affectedAbility instanceof SpellAbility) { - SpellAbility spell = (SpellAbility) affectedAbility; - cardToCheck = spell.getCharacteristics(game); - if (spell.getManaCosts().isEmpty()){ - return false; - } - } - // must be you - if (!playerId.equals(source.getControllerId())) { - return false; - } - - Player cardOwner = game.getPlayer(cardToCheck.getOwnerId()); - Player controller = game.getPlayer(source.getControllerId()); - if (cardOwner == null || controller == null) { - return false; - } - - // must be your or opponents library - switch (this.targetLibrary) { - case YOU: { - Card topCard = controller.getLibrary().getFromTop(game); - if (topCard == null || !topCard.getId().equals(cardToCheck.getMainCard().getId())) { - return false; - } - break; - } - - case OPPONENT: { - if (!game.getOpponents(controller.getId()).contains(cardOwner.getId())) { - return false; - } - Card topCard = cardOwner.getLibrary().getFromTop(game); - if (topCard == null || !topCard.getId().equals(cardToCheck.getMainCard().getId())) { - return false; - } - break; - } - - case SOURCE_TARGETS: { - UUID needCardId = cardToCheck.getMainCard().getId(); - if (CardUtil.getAllSelectedTargets(source, game).stream() - .map(game::getPlayer) - .filter(Objects::nonNull) - .noneMatch(player -> { - Card topCard = player.getLibrary().getFromTop(game); - return topCard != null && topCard.getId().equals(needCardId); - })) { - return false; - } - break; - } - - default: { - return false; - } - } - - // must be correct card - return filter.match(cardToCheck, playerId, source, game); - } -} diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/PlayTheTopCardTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/PlayTheTopCardTargetEffect.java deleted file mode 100644 index 642083049a7..00000000000 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/PlayTheTopCardTargetEffect.java +++ /dev/null @@ -1,22 +0,0 @@ -package mage.abilities.effects.common.continuous; - -import mage.constants.TargetController; - -/** - * @author JayDi85 - */ -public class PlayTheTopCardTargetEffect extends PlayTheTopCardEffect { - - public PlayTheTopCardTargetEffect() { - super(TargetController.SOURCE_TARGETS); - } - - protected PlayTheTopCardTargetEffect(final PlayTheTopCardTargetEffect effect) { - super(effect); - } - - @Override - public PlayTheTopCardTargetEffect copy() { - return new PlayTheTopCardTargetEffect(this); - } -} diff --git a/Mage/src/main/java/mage/abilities/effects/common/cost/FaceDownSpellsCostReductionControllerEffect.java b/Mage/src/main/java/mage/abilities/effects/common/cost/FaceDownSpellsCostReductionControllerEffect.java new file mode 100644 index 00000000000..a8961615b36 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/cost/FaceDownSpellsCostReductionControllerEffect.java @@ -0,0 +1,46 @@ +package mage.abilities.effects.common.cost; + +import mage.abilities.Ability; +import mage.abilities.SpellAbility; +import mage.filter.FilterCard; +import mage.filter.common.FilterCreatureCard; +import mage.game.Game; + +public class FaceDownSpellsCostReductionControllerEffect extends SpellsCostReductionControllerEffect{ + + private static final FilterCreatureCard standardFilter = new FilterCreatureCard("Face-down creature spells"); + + /** + * Face-down creature spells you cast cost + * @param amount less to cast + */ + public FaceDownSpellsCostReductionControllerEffect(int amount) { + super(standardFilter, amount); + } + + /** + * Face-down spells you cast cost + * @param filter with matching characteristics + * @param amount less to cast + */ + public FaceDownSpellsCostReductionControllerEffect(FilterCard filter, int amount) { + super(filter, amount); + } + + protected FaceDownSpellsCostReductionControllerEffect(final FaceDownSpellsCostReductionControllerEffect effect) { + super(effect); + } + + @Override + public FaceDownSpellsCostReductionControllerEffect copy() { + return new FaceDownSpellsCostReductionControllerEffect(this); + } + + @Override + public boolean applies(Ability abilityToModify, Ability source, Game game) { + if (abilityToModify instanceof SpellAbility && ((SpellAbility) abilityToModify).getSpellAbilityCastMode().isFaceDown()) { + return super.applies(abilityToModify, source, game); + } + return false; + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/cost/MorphSpellsCostReductionControllerEffect.java b/Mage/src/main/java/mage/abilities/effects/common/cost/MorphSpellsCostReductionControllerEffect.java deleted file mode 100644 index ffe11991641..00000000000 --- a/Mage/src/main/java/mage/abilities/effects/common/cost/MorphSpellsCostReductionControllerEffect.java +++ /dev/null @@ -1,33 +0,0 @@ -package mage.abilities.effects.common.cost; - -import mage.abilities.Ability; -import mage.abilities.keyword.MorphAbility; -import mage.filter.FilterCard; -import mage.filter.common.FilterCreatureCard; -import mage.game.Game; - -public class MorphSpellsCostReductionControllerEffect extends SpellsCostReductionControllerEffect{ - private static final FilterCreatureCard standardFilter = new FilterCreatureCard("Face-down creature spells"); - - public MorphSpellsCostReductionControllerEffect(int amount) { - super(standardFilter, amount); - } - public MorphSpellsCostReductionControllerEffect(FilterCard filter, int amount) { - super(filter, amount); - } - protected MorphSpellsCostReductionControllerEffect(final MorphSpellsCostReductionControllerEffect effect) { - super(effect); - } - @Override - public MorphSpellsCostReductionControllerEffect copy() { - return new MorphSpellsCostReductionControllerEffect(this); - } - - @Override - public boolean applies(Ability abilityToModify, Ability source, Game game) { - if (abilityToModify instanceof MorphAbility) { - return super.applies(abilityToModify, source, game); - } - return false; - } -} \ No newline at end of file diff --git a/Mage/src/main/java/mage/abilities/effects/common/cost/SpellsCostIncreasingAllEffect.java b/Mage/src/main/java/mage/abilities/effects/common/cost/SpellsCostIncreasingAllEffect.java index ae01a4f8cf7..492bd8ebc6e 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/cost/SpellsCostIncreasingAllEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/cost/SpellsCostIncreasingAllEffect.java @@ -47,13 +47,16 @@ public class SpellsCostIncreasingAllEffect extends CostModificationEffectImpl { private void setText() { StringBuilder sb = new StringBuilder(); - sb.append(filter.getMessage()); + String filterMessage = filter.getMessage(); + sb.append(filterMessage); switch (this.targetController) { case YOU: sb.append(" you cast"); break; case OPPONENT: - sb.append(" your opponents cast"); + if (!filterMessage.contains("your opponents cast")) { + sb.append(" your opponents cast"); + } break; case ACTIVE: sb.append(" the active player casts"); diff --git a/Mage/src/main/java/mage/abilities/effects/common/cost/SpellsCostReductionControllerEffect.java b/Mage/src/main/java/mage/abilities/effects/common/cost/SpellsCostReductionControllerEffect.java index 5650069f91f..7d5b3d8e221 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/cost/SpellsCostReductionControllerEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/cost/SpellsCostReductionControllerEffect.java @@ -34,12 +34,8 @@ public class SpellsCostReductionControllerEffect extends CostModificationEffectI this.amount = 0; this.manaCostsToReduce = manaCostsToReduce; this.upTo = false; - - StringBuilder sb = new StringBuilder(); - sb.append(filter.getMessage()).append(" you cast cost "); - sb.append(manaCostsToReduce.getText()); - sb.append(" less to cast. This effect reduces only the amount of colored mana you pay."); - this.staticText = sb.toString(); + this.staticText = filter.getMessage() + " you cast cost " + manaCostsToReduce.getText() + + " less to cast. This effect reduces only the amount of colored mana you pay."; } public SpellsCostReductionControllerEffect(FilterCard filter, int amount) { diff --git a/Mage/src/main/java/mage/abilities/effects/common/counter/AddCountersAllEffect.java b/Mage/src/main/java/mage/abilities/effects/common/counter/AddCountersAllEffect.java index f632a005bce..8f17cb2017e 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/counter/AddCountersAllEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/counter/AddCountersAllEffect.java @@ -44,29 +44,33 @@ public class AddCountersAllEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); MageObject sourceObject = game.getObject(source); - if (controller != null && sourceObject != null) { - if (counter != null) { - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source, game)) { - Counter newCounter = counter.copy(); - int calculated = amount.calculate(game, source, this); // 0 -- you must use default counter - if (calculated < 0) { - continue; - } else if (calculated == 0) { - // use original counter - } else { - // increase to calculated value - newCounter.remove(newCounter.getCount()); - newCounter.add(calculated); - } - - permanent.addCounters(newCounter, source.getControllerId(), source, game); - if (!game.isSimulation() && newCounter.getCount() > 0) { - game.informPlayers(sourceObject.getLogName() + ": " + controller.getLogName() + " puts " + newCounter.getCount() + ' ' + newCounter.getName() - + (newCounter.getCount() == 1 ? " counter" : " counters") + " on " + permanent.getLogName()); - } - } + if (controller != null && sourceObject != null && counter != null) { + Counter newCounter = counter.copy(); + int calculated = amount.calculate(game, source, this); + if (!(amount instanceof StaticValue) || calculated > 0) { + // If dynamic, or static and set to a > 0 value, we use that instead of the counter's internal amount. + newCounter.remove(newCounter.getCount()); + newCounter.add(calculated); + } else { + // StaticValue 0 -- the default counter has the amount, so no adjustment. } - return true; + + if (newCounter.getCount() <= 0) { + return false; // no need to iterate on the permanents, no counters will be put on them + } + + boolean result = false; + for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source, game)) { + Counter newCounterForPermanent = newCounter.copy(); + + permanent.addCounters(newCounterForPermanent, source.getControllerId(), source, game); + game.informPlayers(sourceObject.getLogName() + ": " + controller.getLogName() + " puts " + + newCounterForPermanent.getCount() + ' ' + newCounterForPermanent.getName() + + (newCounterForPermanent.getCount() == 1 ? " counter" : " counters") + " on " + permanent.getLogName()); + + result |= true; + } + return result; } return false; } diff --git a/Mage/src/main/java/mage/abilities/effects/common/counter/AddCountersTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/counter/AddCountersTargetEffect.java index d3c8314802e..883f5daed5f 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/counter/AddCountersTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/counter/AddCountersTargetEffect.java @@ -56,38 +56,42 @@ public class AddCountersTargetEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); MageObject sourceObject = game.getObject(source); if (controller != null && sourceObject != null && counter != null) { + Counter newCounter = counter.copy(); + int calculated = amount.calculate(game, source, this); + if (!(amount instanceof StaticValue) || calculated > 0) { + // If dynamic, or static and set to a > 0 value, we use that instead of the counter's internal amount. + newCounter.remove(newCounter.getCount()); + newCounter.add(calculated); + } else { + // StaticValue 0 -- the default counter has the amount, so no adjustment. + } + + if (newCounter.getCount() <= 0) { + return false; // no need to iterate on targets, no counters will be put on them + } + int affectedTargets = 0; for (UUID uuid : getTargetPointer().getTargets(game, source)) { - Counter newCounter = counter.copy(); - int calculated = amount.calculate(game, source, this); // 0 -- you must use default couner - if (calculated < 0) { - continue; - } else if (calculated == 0) { - // use original counter - } else { - // increase to calculated value - newCounter.remove(newCounter.getCount()); - newCounter.add(calculated); - } + Counter newCounterForTarget = newCounter.copy(); Permanent permanent = game.getPermanent(uuid); Player player = game.getPlayer(uuid); Card card = game.getCard(getTargetPointer().getFirst(game, source)); if (permanent != null) { - permanent.addCounters(newCounter, source.getControllerId(), source, game); + permanent.addCounters(newCounterForTarget, source.getControllerId(), source, game); affectedTargets++; game.informPlayers(sourceObject.getLogName() + ": " + controller.getLogName() + " puts " - + newCounter.getCount() + ' ' + newCounter.getName() + " counters on " + permanent.getLogName()); + + newCounterForTarget.getCount() + ' ' + newCounterForTarget.getName() + " counters on " + permanent.getLogName()); } else if (player != null) { - player.addCounters(newCounter, source.getControllerId(), source, game); + player.addCounters(newCounterForTarget, source.getControllerId(), source, game); affectedTargets++; game.informPlayers(sourceObject.getLogName() + ": " + controller.getLogName() + " puts " - + newCounter.getCount() + ' ' + newCounter.getName() + " counters on " + player.getLogName()); + + newCounterForTarget.getCount() + ' ' + newCounterForTarget.getName() + " counters on " + player.getLogName()); } else if (card != null) { - card.addCounters(newCounter, source.getControllerId(), source, game); + card.addCounters(newCounterForTarget, source.getControllerId(), source, game); affectedTargets++; game.informPlayers(sourceObject.getLogName() + ": " + controller.getLogName() + " puts " - + newCounter.getCount() + ' ' + newCounter.getName() + " counters on " + card.getLogName()); + + newCounterForTarget.getCount() + ' ' + newCounterForTarget.getName() + " counters on " + card.getLogName()); } } return affectedTargets > 0; diff --git a/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryPutInGraveyardEffect.java b/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryPutInGraveyardEffect.java index 97f250d04a6..03b09ed1a4c 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryPutInGraveyardEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryPutInGraveyardEffect.java @@ -14,9 +14,10 @@ import mage.target.common.TargetCardInLibrary; */ public class SearchLibraryPutInGraveyardEffect extends SearchEffect { - public SearchLibraryPutInGraveyardEffect() { + public SearchLibraryPutInGraveyardEffect(boolean textThatCard) { super(new TargetCardInLibrary(StaticFilters.FILTER_CARD), Outcome.Neutral); - staticText = "search your library for a card, put that card into your graveyard, then shuffle"; + staticText = "search your library for a card, put " + (textThatCard ? "that card" : "it") + + " into your graveyard, then shuffle"; } protected SearchLibraryPutInGraveyardEffect(final SearchLibraryPutInGraveyardEffect effect) { @@ -41,4 +42,4 @@ public class SearchLibraryPutInGraveyardEffect extends SearchEffect { return true; } -} \ No newline at end of file +} diff --git a/Mage/src/main/java/mage/abilities/keyword/BlitzAbility.java b/Mage/src/main/java/mage/abilities/keyword/BlitzAbility.java index 409c1688d03..9453b049fd8 100644 --- a/Mage/src/main/java/mage/abilities/keyword/BlitzAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/BlitzAbility.java @@ -39,7 +39,7 @@ public class BlitzAbility extends SpellAbility { ability.addEffect(new BlitzAddDelayedTriggeredAbilityEffect()); ability.setRuleVisible(false); addSubAbility(ability); - this.ruleAdditionalCostsVisible = false; + this.setAdditionalCostsRuleVisible(false); this.timing = TimingRule.SORCERY; } diff --git a/Mage/src/main/java/mage/abilities/keyword/CollectEvidenceAbility.java b/Mage/src/main/java/mage/abilities/keyword/CollectEvidenceAbility.java index a0883b671d1..d2fb0681fb7 100644 --- a/Mage/src/main/java/mage/abilities/keyword/CollectEvidenceAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/CollectEvidenceAbility.java @@ -41,9 +41,12 @@ public class CollectEvidenceAbility extends StaticAbility implements OptionalAdd } public CollectEvidenceAbility(int amount) { + this(amount, null); + } + public CollectEvidenceAbility(int amount, String extraInfoText) { super(Zone.STACK, null); this.additionalCost = makeCost(amount); - this.rule = additionalCost.getName() + ". " + additionalCost.getReminderText(); + this.rule = additionalCost.getName() + ". " + (extraInfoText == null ? "" : extraInfoText + ". ") + additionalCost.getReminderText(); this.setRuleAtTheTop(true); this.addHint(hint); this.amount = amount; diff --git a/Mage/src/main/java/mage/abilities/keyword/CommanderStormAbility.java b/Mage/src/main/java/mage/abilities/keyword/CommanderStormAbility.java index 4c2f566083a..99909e27fd1 100644 --- a/Mage/src/main/java/mage/abilities/keyword/CommanderStormAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/CommanderStormAbility.java @@ -22,7 +22,7 @@ public class CommanderStormAbility extends TriggeredAbilityImpl { public CommanderStormAbility() { super(Zone.STACK, new CommanderStormEffect()); - this.ruleAtTheTop = true; + this.setRuleAtTheTop(true); } private CommanderStormAbility(final CommanderStormAbility ability) { diff --git a/Mage/src/main/java/mage/abilities/keyword/ConspireAbility.java b/Mage/src/main/java/mage/abilities/keyword/ConspireAbility.java index 90deae0d185..1fd68ab47dc 100644 --- a/Mage/src/main/java/mage/abilities/keyword/ConspireAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/ConspireAbility.java @@ -153,7 +153,7 @@ public class ConspireAbility extends StaticAbility implements OptionalAdditional public ConspireAbility setAddedById(UUID addedById) { this.addedById = addedById; CardUtil.castStream( - this.subAbilities.stream(), + this.getSubAbilities().stream(), ConspireTriggeredAbility.class ).forEach(ability -> ability.setAddedById(addedById)); return this; diff --git a/Mage/src/main/java/mage/abilities/keyword/CrewAbility.java b/Mage/src/main/java/mage/abilities/keyword/CrewAbility.java index db154867f9a..04967f90f02 100644 --- a/Mage/src/main/java/mage/abilities/keyword/CrewAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/CrewAbility.java @@ -72,8 +72,9 @@ public class CrewAbility extends SimpleActivatedAbility { @Override public String getRule() { - return "Crew " + value + " (Tap any number of creatures you control with total power " - + value + " or more: This Vehicle becomes an artifact creature until end of turn.)"; + return "Crew " + value + (this.maxActivationsPerTurn == 1 ? ". Activate only once each turn." : "") + + " (Tap any number of creatures you control with total power " + value + + " or more: This Vehicle becomes an artifact creature until end of turn.)"; } } diff --git a/Mage/src/main/java/mage/abilities/keyword/DisguiseAbility.java b/Mage/src/main/java/mage/abilities/keyword/DisguiseAbility.java index b93d5e313fd..69c8f47b248 100644 --- a/Mage/src/main/java/mage/abilities/keyword/DisguiseAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/DisguiseAbility.java @@ -4,12 +4,14 @@ import mage.abilities.Ability; import mage.abilities.SpellAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.Cost; +import mage.abilities.costs.CostAdjuster; import mage.abilities.costs.Costs; import mage.abilities.costs.CostsImpl; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.costs.mana.ManaCost; import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect; import mage.cards.Card; +import mage.constants.Duration; import mage.constants.SpellAbilityCastMode; import mage.constants.SpellAbilityType; import mage.constants.TimingRule; @@ -68,6 +70,10 @@ public class DisguiseAbility extends SpellAbility { protected Costs disguiseCosts; public DisguiseAbility(Card card, Cost disguiseCost) { + this(card, disguiseCost, null); + } + + public DisguiseAbility(Card card, Cost disguiseCost, CostAdjuster costAdjuster) { super(new GenericManaCost(3), card.getName()); this.timing = TimingRule.SORCERY; this.disguiseCosts = new CostsImpl<>(); @@ -77,13 +83,15 @@ public class DisguiseAbility extends SpellAbility { // face down effect (hidden by default, visible in face down objects) Ability ability = new SimpleStaticAbility(new BecomesFaceDownCreatureEffect( - this.disguiseCosts, BecomesFaceDownCreatureEffect.FaceDownType.DISGUISED)); + this.disguiseCosts, null, Duration.WhileOnBattlefield, + BecomesFaceDownCreatureEffect.FaceDownType.DISGUISED, costAdjuster + )); ability.setWorksFaceDown(true); ability.setRuleVisible(false); addSubAbility(ability); } - private DisguiseAbility(final DisguiseAbility ability) { + protected DisguiseAbility(final DisguiseAbility ability) { super(ability); this.disguiseCosts = ability.disguiseCosts; // can't be changed TODO: looks buggy, need research } diff --git a/Mage/src/main/java/mage/abilities/keyword/EmergeAbility.java b/Mage/src/main/java/mage/abilities/keyword/EmergeAbility.java index 9ef64914e17..54d1c457e7a 100644 --- a/Mage/src/main/java/mage/abilities/keyword/EmergeAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/EmergeAbility.java @@ -110,7 +110,7 @@ public class EmergeAbility extends SpellAbility { this.setCostsTag(EMERGE_ACTIVATION_CREATURE_REFERENCE, mor); //Can access with LKI afterwards return true; } else { - activated = false; + activated = false; // TODO: research, why } } } diff --git a/Mage/src/main/java/mage/abilities/keyword/ForetellAbility.java b/Mage/src/main/java/mage/abilities/keyword/ForetellAbility.java index b7192f9f187..9287ae99694 100644 --- a/Mage/src/main/java/mage/abilities/keyword/ForetellAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/ForetellAbility.java @@ -332,17 +332,17 @@ public class ForetellAbility extends SpecialAction { if (game.getState().getZone(mainCardId) != Zone.EXILED) { return ActivationStatus.getFalse(); } + Integer foretoldTurn = (Integer) game.getState().getValue(mainCardId.toString() + "Foretell Turn Number"); + UUID exileId = (UUID) game.getState().getValue(mainCardId.toString() + "foretellAbility"); // Card must be Foretold - if (game.getState().getValue(mainCardId.toString() + "Foretell Turn Number") == null - && game.getState().getValue(mainCardId + "foretellAbility") == null) { + if (foretoldTurn == null || exileId == null) { return ActivationStatus.getFalse(); } // Can't be cast if the turn it was Foretold is the same - if ((int) game.getState().getValue(mainCardId.toString() + "Foretell Turn Number") == game.getTurnNum()) { + if (foretoldTurn == game.getTurnNum()) { return ActivationStatus.getFalse(); } // Check that the card is actually in the exile zone (ex: Oblivion Ring exiles it after it was Foretold, etc) - UUID exileId = (UUID) game.getState().getValue(mainCardId.toString() + "foretellAbility"); ExileZone exileZone = game.getState().getExile().getExileZone(exileId); if (exileZone != null && exileZone.isEmpty()) { diff --git a/Mage/src/main/java/mage/abilities/keyword/PlotAbility.java b/Mage/src/main/java/mage/abilities/keyword/PlotAbility.java new file mode 100644 index 00000000000..17c667914e0 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/keyword/PlotAbility.java @@ -0,0 +1,325 @@ +package mage.abilities.keyword; + +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.SpecialAction; +import mage.abilities.SpellAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.Costs; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.OneShotEffect; +import mage.cards.*; +import mage.constants.*; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.players.Player; +import mage.util.CardUtil; + +import java.util.List; +import java.util.UUID; + +/** + * @author Susucr + */ +public class PlotAbility extends SpecialAction { + + private final String rule; + + public PlotAbility(String plotCost) { + super(Zone.ALL); // Usually, plot only works from hand. However [[Fblthp, Lost on the Range]] allows plotting from library + this.addCost(new ManaCostsImpl<>(plotCost)); + this.addEffect(new PlotSourceExileEffect()); + this.setTiming(TimingRule.SORCERY); + this.usesStack = false; + this.rule = "Plot " + plotCost; + } + + private PlotAbility(final PlotAbility ability) { + super(ability); + this.rule = ability.rule; + } + + @Override + public PlotAbility copy() { + return new PlotAbility(this); + } + + @Override + public String getRule() { + return rule; + } + + @Override + public ActivationStatus canActivate(UUID playerId, Game game) { + // Plot ability uses card's timing restriction + Card card = game.getCard(getSourceId()); + if (card == null) { + return ActivationStatus.getFalse(); + } + // plot can only be activated from hand or from top of library if allowed to. + Zone zone = game.getState().getZone(getSourceId()); + if (zone == Zone.HAND) { + // Allowed from hand + } else if (zone == Zone.LIBRARY) { + // Allowed only if permitted for top card, and only if the card is on top and is nonland + // Note: if another effect changes zones where permitted, or if different card categories are permitted, + // it would be better to refactor this as an unique AsThoughEffect. + // As of now, only Fblthp, Lost on the Range changes permission of plot. + Player player = game.getPlayer(getControllerId()); + if (player == null || !player.canPlotFromTopOfLibrary()) { + return ActivationStatus.getFalse(); + } + Card topCardLibrary = player.getLibrary().getFromTop(game); + if (topCardLibrary == null || !topCardLibrary.getId().equals(card.getId()) || card.isLand()) { + return ActivationStatus.getFalse(); + } + } else { + // Not Allowed from other zones + return ActivationStatus.getFalse(); + } + if (!card.getSpellAbility().spellCanBeActivatedRegularlyNow(playerId, game)) { + return ActivationStatus.getFalse(); + } + return super.canActivate(playerId, game); + } + + static UUID getPlotExileId(UUID playerId, Game game) { + UUID exileId = (UUID) game.getState().getValue("PlotExileId" + playerId.toString()); + if (exileId == null) { + exileId = UUID.randomUUID(); + game.getState().setValue("PlotExileId" + playerId, exileId); + } + return exileId; + } + + static String getPlotTurnKeyForCard(UUID cardId) { + return cardId.toString() + "|" + "Plotted Turn"; + } + + /** + * To be used in an OneShotEffect's apply. + * 'Plot' the provided card. The card is exiled in it's owner plot zone, + * and may be cast by that player without paying its mana cost at sorcery + * speed on a future turn. + */ + public static boolean doExileAndPlotCard(Card card, Game game, Ability source) { + if (card == null) { + return false; + } + Player owner = game.getPlayer(card.getOwnerId()); + if (owner == null) { + return false; + } + UUID exileId = PlotAbility.getPlotExileId(owner.getId(), game); + String exileZoneName = "Plots of " + owner.getName(); + Card mainCard = card.getMainCard(); + Zone zone = game.getState().getZone(mainCard.getId()); + if (mainCard.moveToExile(exileId, exileZoneName, source, game)) { + // Remember on which turn the card was last plotted. + game.getState().setValue(PlotAbility.getPlotTurnKeyForCard(mainCard.getId()), game.getTurnNum()); + game.addEffect(new PlotAddSpellAbilityEffect(new MageObjectReference(mainCard, game)), source); + game.informPlayers( + owner.getLogName() + + " plots " + mainCard.getLogName() + + " from " + zone.toString().toLowerCase() + + CardUtil.getSourceLogName(game, source, card.getId()) + ); + game.fireEvent(GameEvent.getEvent(GameEvent.EventType.BECOME_PLOTTED, mainCard.getId(), source, owner.getId())); + } + return true; + } +} + +/** + * Exile the source card in the plot exile zone of its owner + * and allow its owner to cast it at sorcery speed starting + * next turn. + */ +class PlotSourceExileEffect extends OneShotEffect { + + PlotSourceExileEffect() { + super(Outcome.Benefit); + } + + private PlotSourceExileEffect(final PlotSourceExileEffect effect) { + super(effect); + } + + @Override + public PlotSourceExileEffect copy() { + return new PlotSourceExileEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return PlotAbility.doExileAndPlotCard(game.getCard(source.getSourceId()), game, source); + } +} + +class PlotAddSpellAbilityEffect extends ContinuousEffectImpl { + + private final MageObjectReference mor; + + PlotAddSpellAbilityEffect(MageObjectReference mor) { + super(Duration.EndOfGame, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); + this.mor = mor; + staticText = "Plot card"; + } + + private PlotAddSpellAbilityEffect(final PlotAddSpellAbilityEffect effect) { + super(effect); + this.mor = effect.mor; + } + + @Override + public PlotAddSpellAbilityEffect copy() { + return new PlotAddSpellAbilityEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Card card = mor.getCard(game); + if (card == null) { + discard(); + return true; + } + + Card mainCard = card.getMainCard(); + UUID mainCardId = mainCard.getId(); + Player player = game.getPlayer(card.getOwnerId()); + if (game.getState().getZone(mainCardId) != Zone.EXILED || player == null) { + discard(); + return true; + } + + List faces = CardUtil.getCastableComponents(mainCard, null, source, player, game, null, false); + for (Card face : faces) { + // Add the spell ability to each castable face to have the proper name/paramaters. + PlotSpellAbility ability = new PlotSpellAbility(face.getName()); + ability.setSourceId(face.getId()); + ability.setControllerId(player.getId()); + ability.setSpellAbilityType(face.getSpellAbility().getSpellAbilityType()); + game.getState().addOtherAbility(face, ability); + } + return true; + } +} + +/** + * This is inspired (after a little cleanup) by how {@link ForetellAbility} does it. + */ +class PlotSpellAbility extends SpellAbility { + + private String faceCardName; // Same as with Foretell, we identify the proper face with its spell name. + private SpellAbility spellAbilityToResolve; + + PlotSpellAbility(String faceCardName) { + super(null, faceCardName, Zone.EXILED, SpellAbilityType.BASE_ALTERNATE, SpellAbilityCastMode.PLOT); + this.setRuleVisible(false); + this.setAdditionalCostsRuleVisible(false); + this.faceCardName = faceCardName; + this.addCost(new ManaCostsImpl<>("{0}")); + } + + private PlotSpellAbility(final PlotSpellAbility ability) { + super(ability); + this.faceCardName = ability.faceCardName; + this.spellAbilityToResolve = ability.spellAbilityToResolve; + } + + @Override + public PlotSpellAbility copy() { + return new PlotSpellAbility(this); + } + + @Override + public ActivationStatus canActivate(UUID playerId, Game game) { + if (super.canActivate(playerId, game).canActivate()) { + Card card = game.getCard(getSourceId()); + if (card != null) { + Card mainCard = card.getMainCard(); + UUID mainCardId = mainCard.getId(); + // Card must be in the exile zone + if (game.getState().getZone(mainCardId) != Zone.EXILED) { + return ActivationStatus.getFalse(); + } + Integer plottedTurn = (Integer) game.getState().getValue(PlotAbility.getPlotTurnKeyForCard(mainCardId)); + // Card must have been plotted + if (plottedTurn == null) { + return ActivationStatus.getFalse(); + } + // Can't be cast if the turn it was last Plotted is the same + if (plottedTurn == game.getTurnNum()) { + return ActivationStatus.getFalse(); + } + // Only allow the cast at sorcery speed + if (!game.canPlaySorcery(playerId)) { + return ActivationStatus.getFalse(); + } + // Check that the proper face can be cast. + // TODO: As with Foretell, this does not look very clean. Is the face card sometimes incorrect on calling canActivate? + if (mainCard instanceof CardWithHalves) { + if (((CardWithHalves) mainCard).getLeftHalfCard().getName().equals(faceCardName)) { + return ((CardWithHalves) mainCard).getLeftHalfCard().getSpellAbility().canActivate(playerId, game); + } else if (((CardWithHalves) mainCard).getRightHalfCard().getName().equals(faceCardName)) { + return ((CardWithHalves) mainCard).getRightHalfCard().getSpellAbility().canActivate(playerId, game); + } + } else if (card instanceof AdventureCard) { + if (card.getMainCard().getName().equals(faceCardName)) { + return card.getMainCard().getSpellAbility().canActivate(playerId, game); + } else if (((AdventureCard) card).getSpellCard().getName().equals(faceCardName)) { + return ((AdventureCard) card).getSpellCard().getSpellAbility().canActivate(playerId, game); + } + } + return card.getSpellAbility().canActivate(playerId, game); + } + } + return ActivationStatus.getFalse(); + } + + @Override + public SpellAbility getSpellAbilityToResolve(Game game) { + Card card = game.getCard(getSourceId()); + if (card != null) { + if (spellAbilityToResolve == null) { + SpellAbility spellAbilityCopy = null; + // TODO: As with Foretell, this does not look very clean. Is the face card sometimes incorrect on calling getSpellAbilityToResolve? + if (card instanceof CardWithHalves) { + if (((CardWithHalves) card).getLeftHalfCard().getName().equals(faceCardName)) { + spellAbilityCopy = ((CardWithHalves) card).getLeftHalfCard().getSpellAbility().copy(); + } else if (((CardWithHalves) card).getRightHalfCard().getName().equals(faceCardName)) { + spellAbilityCopy = ((CardWithHalves) card).getRightHalfCard().getSpellAbility().copy(); + } + } else if (card instanceof AdventureCard) { + if (card.getMainCard().getName().equals(faceCardName)) { + spellAbilityCopy = card.getMainCard().getSpellAbility().copy(); + } else if (((AdventureCard) card).getSpellCard().getName().equals(faceCardName)) { + spellAbilityCopy = ((AdventureCard) card).getSpellCard().getSpellAbility().copy(); + } + } else { + spellAbilityCopy = card.getSpellAbility().copy(); + } + if (spellAbilityCopy == null) { + return null; + } + spellAbilityCopy.setId(this.getId()); + spellAbilityCopy.clearManaCosts(); + spellAbilityCopy.clearManaCostsToPay(); + spellAbilityCopy.addCost(this.getCosts().copy()); + spellAbilityCopy.addCost(this.getManaCosts().copy()); + spellAbilityCopy.setSpellAbilityCastMode(this.getSpellAbilityCastMode()); + spellAbilityToResolve = spellAbilityCopy; + } + } + return spellAbilityToResolve; + } + + @Override + public Costs getCosts() { + if (spellAbilityToResolve == null) { + return super.getCosts(); + } + return spellAbilityToResolve.getCosts(); + } +} \ No newline at end of file diff --git a/Mage/src/main/java/mage/abilities/keyword/SaddleAbility.java b/Mage/src/main/java/mage/abilities/keyword/SaddleAbility.java new file mode 100644 index 00000000000..3add5303ef5 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/keyword/SaddleAbility.java @@ -0,0 +1,179 @@ +package mage.abilities.keyword; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.condition.common.SaddledCondition; +import mage.abilities.costs.Cost; +import mage.abilities.costs.CostImpl; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.hint.ConditionHint; +import mage.abilities.hint.Hint; +import mage.abilities.hint.HintUtils; +import mage.constants.*; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.filter.predicate.permanent.TappedPredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.target.Target; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.watchers.common.SaddledMountWatcher; + +import java.awt.*; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public class SaddleAbility extends SimpleActivatedAbility { + + private final int value; + private static final Hint hint = new ConditionHint(SaddledCondition.instance, "This permanent is saddled"); + + public SaddleAbility(int value) { + super(new SaddleEffect(), new SaddleCost(value)); + this.value = value; + this.addHint(hint); + this.setTiming(TimingRule.SORCERY); + this.addWatcher(new SaddledMountWatcher()); + } + + private SaddleAbility(final SaddleAbility ability) { + super(ability); + this.value = ability.value; + } + + @Override + public SaddleAbility copy() { + return new SaddleAbility(this); + } + + @Override + public String getRule() { + return "Saddle " + value + " (Tap any number of other creatures you control with total power " + + value + " or more: This Mount becomes saddled until end of turn. Saddle only as a sorcery.)"; + } +} + +class SaddleEffect extends ContinuousEffectImpl { + + SaddleEffect() { + super(Duration.EndOfTurn, Layer.RulesEffects, SubLayer.NA, Outcome.Benefit); + } + + private SaddleEffect(final SaddleEffect effect) { + super(effect); + } + + @Override + public SaddleEffect copy() { + return new SaddleEffect(this); + } + + @Override + public void init(Ability source, Game game) { + super.init(source, game); + game.fireEvent(GameEvent.getEvent( + GameEvent.EventType.MOUNT_SADDLED, + source.getSourceId(), + source, source.getControllerId() + )); + } + + @Override + public boolean apply(Game game, Ability source) { + Optional.ofNullable(source.getSourcePermanentIfItStillExists(game)) + .ifPresent(permanent -> permanent.setSaddled(true)); + return true; + } +} + +class SaddleCost extends CostImpl { + + + private static final FilterControlledCreaturePermanent filter + = new FilterControlledCreaturePermanent("another untapped creature you control"); + + static { + filter.add(TappedPredicate.UNTAPPED); + filter.add(AnotherPredicate.instance); + } + + private final int value; + + SaddleCost(int value) { + this.value = value; + } + + private SaddleCost(final SaddleCost cost) { + super(cost); + this.value = cost.value; + } + + @Override + public boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana, Cost costToPay) { + Target target = new TargetControlledCreaturePermanent(0, Integer.MAX_VALUE, filter, true) { + @Override + public String getMessage() { + // shows selected power + int selectedPower = this.targets.keySet().stream() + .map(game::getPermanent) + .filter(Objects::nonNull) + .map(MageObject::getPower) + .mapToInt(MageInt::getValue) + .sum(); + String extraInfo = "(selected power " + selectedPower + " of " + value + ")"; + if (selectedPower >= value) { + extraInfo = HintUtils.prepareText(extraInfo, Color.GREEN); + } + return super.getMessage() + " " + extraInfo; + } + }; + + // can cancel + if (target.choose(Outcome.Tap, controllerId, source.getSourceId(), source, game)) { + int sumPower = 0; + for (UUID targetId : target.getTargets()) { + GameEvent event = new GameEvent(GameEvent.EventType.SADDLE_MOUNT, targetId, source, controllerId); + if (!game.replaceEvent(event)) { + Permanent permanent = game.getPermanent(targetId); + if (permanent != null && permanent.tap(source, game)) { + sumPower += permanent.getPower().getValue(); + } + } + } + paid = sumPower >= value; + if (paid) { + for (UUID targetId : target.getTargets()) { + game.fireEvent(GameEvent.getEvent(GameEvent.EventType.SADDLED_MOUNT, targetId, source, controllerId)); + } + } + } else { + return false; + } + + return paid; + } + + @Override + public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { + int sumPower = 0; + for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, controllerId, game)) { + sumPower += Math.max(permanent.getPower().getValue(), 0); + if (sumPower >= value) { + return true; + } + } + return false; + } + + @Override + public SaddleCost copy() { + return new SaddleCost(this); + } +} diff --git a/Mage/src/main/java/mage/abilities/keyword/SpreeAbility.java b/Mage/src/main/java/mage/abilities/keyword/SpreeAbility.java new file mode 100644 index 00000000000..c3f9672ac7c --- /dev/null +++ b/Mage/src/main/java/mage/abilities/keyword/SpreeAbility.java @@ -0,0 +1,28 @@ +package mage.abilities.keyword; + +import mage.abilities.StaticAbility; +import mage.cards.Card; +import mage.constants.Zone; + +/** + * @author TheElk801 + */ +public class SpreeAbility extends StaticAbility { + + public SpreeAbility(Card card) { + super(Zone.ALL, null); + this.setRuleVisible(false); + card.getSpellAbility().getModes().setChooseText("Spree (Choose one or more additional costs.)"); + card.getSpellAbility().getModes().setMinModes(1); + card.getSpellAbility().getModes().setMaxModes(Integer.MAX_VALUE); + } + + private SpreeAbility(final SpreeAbility ability) { + super(ability); + } + + @Override + public SpreeAbility copy() { + return new SpreeAbility(this); + } +} diff --git a/Mage/src/main/java/mage/abilities/keyword/SquadAbility.java b/Mage/src/main/java/mage/abilities/keyword/SquadAbility.java index ca7710171b6..8c0a232a9e6 100644 --- a/Mage/src/main/java/mage/abilities/keyword/SquadAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/SquadAbility.java @@ -5,7 +5,6 @@ import mage.abilities.SpellAbility; import mage.abilities.StaticAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.costs.*; -import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.CreateTokenCopySourceEffect; import mage.abilities.effects.OneShotEffect; @@ -25,9 +24,6 @@ public class SquadAbility extends StaticAbility implements OptionalAdditionalSou protected static final String SQUAD_ACTIVATION_VALUE_KEY = "squadActivationCount"; protected static final String SQUAD_REMINDER = "You may pay an additional " + "{cost} any number of times as you cast this spell."; - public SquadAbility() { - this(new GenericManaCost(2)); - } public SquadAbility(Cost cost) { super(Zone.STACK, null); diff --git a/Mage/src/main/java/mage/abilities/keyword/StormAbility.java b/Mage/src/main/java/mage/abilities/keyword/StormAbility.java index 014b160ccaa..bffd2ef6151 100644 --- a/Mage/src/main/java/mage/abilities/keyword/StormAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/StormAbility.java @@ -6,6 +6,7 @@ import mage.abilities.TriggeredAbilityImpl; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; +import mage.abilities.hint.Hint; import mage.abilities.hint.ValueHint; import mage.constants.Outcome; import mage.constants.Zone; @@ -21,9 +22,15 @@ import org.apache.log4j.Logger; */ public class StormAbility extends TriggeredAbilityImpl { + private static final Hint hint = new ValueHint("Spells cast this turn", SpellsCastThisTurnValue.instance); + + public static Hint getHint() { + return hint; + } + public StormAbility() { super(Zone.STACK, new StormEffect()); - this.addHint(new ValueHint("Spells cast this turn", SpellsCastThisTurnValue.instance)); + this.addHint(hint); } private StormAbility(final StormAbility ability) { diff --git a/Mage/src/main/java/mage/abilities/keyword/WardAbility.java b/Mage/src/main/java/mage/abilities/keyword/WardAbility.java index 604828361f2..b6bebf87769 100644 --- a/Mage/src/main/java/mage/abilities/keyword/WardAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/WardAbility.java @@ -81,7 +81,7 @@ public class WardAbility extends TriggeredAbilityImpl { if (targetingObject == null || !game.getOpponents(getControllerId()).contains(targetingObject.getControllerId())) { return false; } - if (CardUtil.checkTargetedEventAlreadyUsed(this.id.toString(), targetingObject, event, game)) { + if (CardUtil.checkTargetedEventAlreadyUsed(this.getId().toString(), targetingObject, event, game)) { return false; } getEffects().setTargetPointer(new FixedTarget(targetingObject.getId())); diff --git a/Mage/src/main/java/mage/cards/CardImpl.java b/Mage/src/main/java/mage/cards/CardImpl.java index 87c2305f867..1ec3d688aab 100644 --- a/Mage/src/main/java/mage/cards/CardImpl.java +++ b/Mage/src/main/java/mage/cards/CardImpl.java @@ -605,6 +605,14 @@ public abstract class CardImpl extends MageObjectImpl implements Card { ability.setRuleVisible(true); } } + // The current face down implementation is just setting a boolean, so any trigger checking for a + // permanent property once being turned face up is not seeing the right face up data. + // For instance triggers looking for specific subtypes being turned face up (Detectives in MKM set) + // are broken without that processAction call. + // This is somewhat a band-aid on the special action nature of turning a permanent face up. + // 708.8. As a face-down permanent is turned face up, its copiable values revert to its normal copiable values. + // Any effects that have been applied to the face-down permanent still apply to the face-up permanent. + game.getState().processAction(game); game.fireEvent(GameEvent.getEvent(GameEvent.EventType.TURNED_FACE_UP, getId(), source, playerId)); return true; } diff --git a/Mage/src/main/java/mage/cards/decks/CardNameUtil.java b/Mage/src/main/java/mage/cards/decks/CardNameUtil.java index eced59de623..5a72291c8c9 100644 --- a/Mage/src/main/java/mage/cards/decks/CardNameUtil.java +++ b/Mage/src/main/java/mage/cards/decks/CardNameUtil.java @@ -32,7 +32,8 @@ public class CardNameUtil { .replace("ü", "u") .replace("É", "E") .replace("ñ", "n") - .replace("®", ""); + .replace("®", "") + .replace("—", ""); } private CardNameUtil() { diff --git a/Mage/src/main/java/mage/constants/CastManaAdjustment.java b/Mage/src/main/java/mage/constants/CastManaAdjustment.java new file mode 100644 index 00000000000..ab094c16ab4 --- /dev/null +++ b/Mage/src/main/java/mage/constants/CastManaAdjustment.java @@ -0,0 +1,29 @@ +package mage.constants; + +/** + * Groups together the most usual ways a card's payment is adjusted + * by card effects that allow play or cast. + *

+ * Effects should attempt to support those for all the various ways + * to play/cast cards/spells in Effects + * + * @author Susucr + */ +public enum CastManaAdjustment { + /** + * No adjustment to play/cast + */ + NONE, + /** + * Mana can be used as any mana type to pay for the mana cost + */ + AS_THOUGH_ANY_MANA_TYPE, + /** + * Mana can be used as any mana color to pay for the mana cost + */ + AS_THOUGH_ANY_MANA_COLOR, + /** + * The card is play/cast without paying for its mana cost + */ + WITHOUT_PAYING_MANA_COST, +} \ No newline at end of file diff --git a/Mage/src/main/java/mage/constants/SpellAbilityCastMode.java b/Mage/src/main/java/mage/constants/SpellAbilityCastMode.java index 2c73fb3c439..a57e28325f6 100644 --- a/Mage/src/main/java/mage/constants/SpellAbilityCastMode.java +++ b/Mage/src/main/java/mage/constants/SpellAbilityCastMode.java @@ -21,7 +21,8 @@ public enum SpellAbilityCastMode { DISGUISE("Disguise", false, true), TRANSFORMED("Transformed", true), DISTURB("Disturb", true), - MORE_THAN_MEETS_THE_EYE("More than Meets the Eye", true); + MORE_THAN_MEETS_THE_EYE("More than Meets the Eye", true), + PLOT("Plot"); private final String text; @@ -91,6 +92,7 @@ public enum SpellAbilityCastMode { case MADNESS: case FLASHBACK: case DISTURB: + case PLOT: case MORE_THAN_MEETS_THE_EYE: // it changes only cost, so keep other characteristics // TODO: research - why TRANSFORMED here - is it used in this.isTransformed code?! diff --git a/Mage/src/main/java/mage/constants/SubType.java b/Mage/src/main/java/mage/constants/SubType.java index 73e412a22f5..40d0087fbca 100644 --- a/Mage/src/main/java/mage/constants/SubType.java +++ b/Mage/src/main/java/mage/constants/SubType.java @@ -71,18 +71,19 @@ public enum SubType { ANGEL("Angel", SubTypeSet.CreatureType), ANTELOPE("Antelope", SubTypeSet.CreatureType), ANZELLAN("Anzellan", SubTypeSet.CreatureType, true), // Star Wars - AQUALISH("Aqualish", SubTypeSet.CreatureType, true), // Star Wars APE("Ape", SubTypeSet.CreatureType), - ARCONA("Arcona", SubTypeSet.CreatureType, true), + AQUALISH("Aqualish", SubTypeSet.CreatureType, true), // Star Wars ARCHER("Archer", SubTypeSet.CreatureType), ARCHON("Archon", SubTypeSet.CreatureType), - ARTIFICER("Artificer", SubTypeSet.CreatureType), + ARCONA("Arcona", SubTypeSet.CreatureType, true), + ARMADILLO("Armadillo", SubTypeSet.CreatureType), ARMY("Army", SubTypeSet.CreatureType), + ARTIFICER("Artificer", SubTypeSet.CreatureType), ASSASSIN("Assassin", SubTypeSet.CreatureType), ASSEMBLY_WORKER("Assembly-Worker", SubTypeSet.CreatureType), ASTARTES("Astartes", SubTypeSet.CreatureType), - ATOG("Atog", SubTypeSet.CreatureType), ATAT("AT-AT", SubTypeSet.CreatureType, true), + ATOG("Atog", SubTypeSet.CreatureType), AUROCHS("Aurochs", SubTypeSet.CreatureType), AUTOBOT("Autobot", SubTypeSet.CreatureType, true), // H17, Grimlock AVATAR("Avatar", SubTypeSet.CreatureType), @@ -96,6 +97,7 @@ public enum SubType { BAT("Bat", SubTypeSet.CreatureType), BEAR("Bear", SubTypeSet.CreatureType), BEAST("Beast", SubTypeSet.CreatureType), + BEAVER("Beaver", SubTypeSet.CreatureType), BEEBLE("Beeble", SubTypeSet.CreatureType), BEHOLDER("Beholder", SubTypeSet.CreatureType), BERSERKER("Berserker", SubTypeSet.CreatureType), @@ -107,6 +109,7 @@ public enum SubType { BRINGER("Bringer", SubTypeSet.CreatureType), BRUSHWAGG("Brushwagg", SubTypeSet.CreatureType), // C + CTAN("C'tan", SubTypeSet.CreatureType), CALAMARI("Calamari", SubTypeSet.CreatureType, true), // Star Wars CAMARID("Camarid", SubTypeSet.CreatureType), CAMEL("Camel", SubTypeSet.CreatureType), @@ -115,8 +118,8 @@ public enum SubType { CARRIER("Carrier", SubTypeSet.CreatureType), CAT("Cat", SubTypeSet.CreatureType), CENTAUR("Centaur", SubTypeSet.CreatureType), - CEREAN("Cerean", SubTypeSet.CreatureType, true), // Star Wars CEPHALID("Cephalid", SubTypeSet.CreatureType), + CEREAN("Cerean", SubTypeSet.CreatureType, true), // Star Wars CHIMERA("Chimera", SubTypeSet.CreatureType), CHISS("Chiss", SubTypeSet.CreatureType, true), CITIZEN("Citizen", SubTypeSet.CreatureType), @@ -127,17 +130,17 @@ public enum SubType { CONSTRUCT("Construct", SubTypeSet.CreatureType), COW("Cow", SubTypeSet.CreatureType, true), // Unglued COWARD("Coward", SubTypeSet.CreatureType), + COYOTE("Coyote", SubTypeSet.CreatureType), CRAB("Crab", SubTypeSet.CreatureType), CROCODILE("Crocodile", SubTypeSet.CreatureType), CROLUTE("Crolute", SubTypeSet.CreatureType, true), // Star Wars - CTAN("C'tan", SubTypeSet.CreatureType), CUSTODES("Custodes", SubTypeSet.CreatureType), CYBERMAN("Cyberman", SubTypeSet.CreatureType), CYBORG("Cyborg", SubTypeSet.CreatureType, true), // Star Wars CYCLOPS("Cyclops", SubTypeSet.CreatureType), // D DALEK("Dalek", SubTypeSet.CreatureType), - DATHOMIRIAN("Dathomirian", SubTypeSet.CreatureType, true), // Star Wars, + DATHOMIRIAN("Dathomirian", SubTypeSet.CreatureType, true), // Star Wars DAUTHI("Dauthi", SubTypeSet.CreatureType), DEMIGOD("Demigod", SubTypeSet.CreatureType), DEMON("Demon", SubTypeSet.CreatureType), @@ -151,9 +154,9 @@ public enum SubType { DRAGON("Dragon", SubTypeSet.CreatureType), DRAKE("Drake", SubTypeSet.CreatureType), DREADNOUGHT("Dreadnought", SubTypeSet.CreatureType), + DROID("Droid", SubTypeSet.CreatureType, true), // Star Wars DRONE("Drone", SubTypeSet.CreatureType), DRUID("Druid", SubTypeSet.CreatureType), - DROID("Droid", SubTypeSet.CreatureType, true), // Star Wars DRYAD("Dryad", SubTypeSet.CreatureType), DWARF("Dwarf", SubTypeSet.CreatureType), // E @@ -166,9 +169,9 @@ public enum SubType { ELF("Elf", SubTypeSet.CreatureType), ELK("Elk", SubTypeSet.CreatureType), EMPLOYEE("Employee", SubTypeSet.CreatureType), - EYE("Eye", SubTypeSet.CreatureType), EWOK("Ewok", SubTypeSet.CreatureType, true), // Star Wars EXPANSION_SYMBOL("Expansion-Symbol", SubTypeSet.CreatureType, true), // Unhinged + EYE("Eye", SubTypeSet.CreatureType), // F FAERIE("Faerie", SubTypeSet.CreatureType), FERRET("Ferret", SubTypeSet.CreatureType), @@ -186,12 +189,12 @@ public enum SubType { GERM("Germ", SubTypeSet.CreatureType), GIANT("Giant", SubTypeSet.CreatureType), GITH("Gith", SubTypeSet.CreatureType), - GNOME("Gnome", SubTypeSet.CreatureType), GNOLL("Gnoll", SubTypeSet.CreatureType), - GOLEM("Golem", SubTypeSet.CreatureType), + GNOME("Gnome", SubTypeSet.CreatureType), GOAT("Goat", SubTypeSet.CreatureType), GOBLIN("Goblin", SubTypeSet.CreatureType), GOD("God", SubTypeSet.CreatureType), + GOLEM("Golem", SubTypeSet.CreatureType), GORGON("Gorgon", SubTypeSet.CreatureType), GRAVEBORN("Graveborn", SubTypeSet.CreatureType), GREMLIN("Gremlin", SubTypeSet.CreatureType), @@ -226,7 +229,6 @@ public enum SubType { // J JACKAL("Jackal", SubTypeSet.CreatureType), JAWA("Jawa", SubTypeSet.CreatureType, true), - JAYA("Jaya", SubTypeSet.PlaneswalkerType), JEDI("Jedi", SubTypeSet.CreatureType, true), // Star Wars JELLYFISH("Jellyfish", SubTypeSet.CreatureType), JUGGERNAUT("Juggernaut", SubTypeSet.CreatureType), @@ -253,7 +255,6 @@ public enum SubType { LIZARD("Lizard", SubTypeSet.CreatureType), LLAMA("Llama", SubTypeSet.CreatureType), LOBSTER("Lobster", SubTypeSet.CreatureType, true), // Unglued - LUKE("Luke", SubTypeSet.PlaneswalkerType, true), // Star Wars // M MANTELLIAN("Mantellian", SubTypeSet.CreatureType, true), // Star Wars MANTICORE("Manticore", SubTypeSet.CreatureType), @@ -271,6 +272,7 @@ public enum SubType { MONK("Monk", SubTypeSet.CreatureType), MONKEY("Monkey", SubTypeSet.CreatureType), MOONFOLK("Moonfolk", SubTypeSet.CreatureType), + MOUNT("Mount", SubTypeSet.CreatureType), MOUSE("Mouse", SubTypeSet.CreatureType), MUTANT("Mutant", SubTypeSet.CreatureType), MYR("Myr", SubTypeSet.CreatureType), @@ -314,6 +316,8 @@ public enum SubType { PINCHER("Pincher", SubTypeSet.CreatureType), PIRATE("Pirate", SubTypeSet.CreatureType), PLANT("Plant", SubTypeSet.CreatureType), + PORCUPINE("Porcupine", SubTypeSet.CreatureType), + POSSUM("Possum", SubTypeSet.CreatureType), PRAETOR("Praetor", SubTypeSet.CreatureType), PRIMARCH("Primarch", SubTypeSet.CreatureType), PRISM("Prism", SubTypeSet.CreatureType), @@ -378,28 +382,28 @@ public enum SubType { SPONGE("Sponge", SubTypeSet.CreatureType), SQUID("Squid", SubTypeSet.CreatureType), SQUIRREL("Squirrel", SubTypeSet.CreatureType), - SNOKE("Snoke", SubTypeSet.PlaneswalkerType, true), // Star Wars STARFISH("Starfish", SubTypeSet.CreatureType), STARSHIP("Starship", SubTypeSet.CreatureType, true), // Star Wars SULLUSTAN("Sullustan", SubTypeSet.CreatureType, true), // Star Wars SURRAKAR("Surrakar", SubTypeSet.CreatureType), SURVIVOR("Survivor", SubTypeSet.CreatureType), + SYNTH("Synth", SubTypeSet.CreatureType), // T TENTACLE("Tentacle", SubTypeSet.CreatureType), TETRAVITE("Tetravite", SubTypeSet.CreatureType), THALAKOS("Thalakos", SubTypeSet.CreatureType), THOPTER("Thopter", SubTypeSet.CreatureType), + THRULL("Thrull", SubTypeSet.CreatureType), TIEFLING("Tiefling", SubTypeSet.CreatureType), TIME_LORD("Time Lord", SubTypeSet.CreatureType), TRANDOSHAN("Trandoshan", SubTypeSet.CreatureType, true), // Star Wars - THRULL("Thrull", SubTypeSet.CreatureType), TREEFOLK("Treefolk", SubTypeSet.CreatureType), + TRILOBITE("Trilobite", SubTypeSet.CreatureType), TRISKELAVITE("Triskelavite", SubTypeSet.CreatureType), TROLL("Troll", SubTypeSet.CreatureType), + TROOPER("Trooper", SubTypeSet.CreatureType, true), // Star Wars TURTLE("Turtle", SubTypeSet.CreatureType), TUSKEN("Tusken", SubTypeSet.CreatureType, true), // Star Wars - TROOPER("Trooper", SubTypeSet.CreatureType, true), // Star Wars - TRILOBITE("Trilobite", SubTypeSet.CreatureType), TWILEK("Twi'lek", SubTypeSet.CreatureType, true), // Star Wars TYRANID("Tyranid", SubTypeSet.CreatureType), // U @@ -407,6 +411,7 @@ public enum SubType { UNICORN("Unicorn", SubTypeSet.CreatureType), // V VAMPIRE("Vampire", SubTypeSet.CreatureType), + VARMINT("Varmint", SubTypeSet.CreatureType), VEDALKEN("Vedalken", SubTypeSet.CreatureType), VIASHINO("Viashino", SubTypeSet.CreatureType), VILLAIN("Villain", SubTypeSet.CreatureType, true), // Unstable @@ -468,6 +473,7 @@ public enum SubType { INZERVA("Inzerva", SubTypeSet.PlaneswalkerType), JACE("Jace", SubTypeSet.PlaneswalkerType), JARED("Jared", SubTypeSet.PlaneswalkerType), + JAYA("Jaya", SubTypeSet.PlaneswalkerType), JESKA("Jeska", SubTypeSet.PlaneswalkerType), KAITO("Kaito", SubTypeSet.PlaneswalkerType), KARN("Karn", SubTypeSet.PlaneswalkerType), @@ -476,8 +482,9 @@ public enum SubType { KIORA("Kiora", SubTypeSet.PlaneswalkerType), KOTH("Koth", SubTypeSet.PlaneswalkerType), LILIANA("Liliana", SubTypeSet.PlaneswalkerType), - LUKKA("Lukka", SubTypeSet.PlaneswalkerType), LOLTH("Lolth", SubTypeSet.PlaneswalkerType), + LUKE("Luke", SubTypeSet.PlaneswalkerType, true), // Star Wars + LUKKA("Lukka", SubTypeSet.PlaneswalkerType), MINSC("Minsc", SubTypeSet.PlaneswalkerType), MORDENKAINEN("Mordenkainen", SubTypeSet.PlaneswalkerType), NAHIRI("Nahiri", SubTypeSet.PlaneswalkerType), @@ -497,6 +504,7 @@ public enum SubType { SERRA("Serra", SubTypeSet.PlaneswalkerType), SIDIOUS("Sidious", SubTypeSet.PlaneswalkerType, true), // Star Wars SIVITRI("Sivitri", SubTypeSet.PlaneswalkerType), + SNOKE("Snoke", SubTypeSet.PlaneswalkerType, true), // Star Wars SORIN("Sorin", SubTypeSet.PlaneswalkerType), SZAT("Szat", SubTypeSet.PlaneswalkerType), TAMIYO("Tamiyo", SubTypeSet.PlaneswalkerType), @@ -577,6 +585,11 @@ public enum SubType { return description; } + // note: does not account for irregular plurals + public String getPluralName() { + return description.endsWith("y") ? description.substring(0, description.length() - 1) + "ies" : description + 's'; + } + @Override public String toString() { return description; diff --git a/Mage/src/main/java/mage/counters/CounterType.java b/Mage/src/main/java/mage/counters/CounterType.java index c509013a28b..9df086acc4d 100644 --- a/Mage/src/main/java/mage/counters/CounterType.java +++ b/Mage/src/main/java/mage/counters/CounterType.java @@ -123,6 +123,7 @@ public enum CounterType { LANDMARK("landmark"), LEVEL("level"), LIFELINK("lifelink"), + LOOT("loot"), LORE("lore"), LUCK("luck"), LOYALTY("loyalty"), diff --git a/Mage/src/main/java/mage/filter/StaticFilters.java b/Mage/src/main/java/mage/filter/StaticFilters.java index 87c6b5af9a5..848b6a6026f 100644 --- a/Mage/src/main/java/mage/filter/StaticFilters.java +++ b/Mage/src/main/java/mage/filter/StaticFilters.java @@ -1060,6 +1060,14 @@ public final class StaticFilters { FILTER_CREATURE_NON_TOKEN.setLockedFilter(true); } + + public static final FilterCreaturePermanent FILTER_CREATURES_NON_TOKEN = new FilterCreaturePermanent("nontoken creatures"); + + static { + FILTER_CREATURES_NON_TOKEN.add(TokenPredicate.FALSE); + FILTER_CREATURES_NON_TOKEN.setLockedFilter(true); + } + public static final FilterControlledCreaturePermanent FILTER_A_CONTROLLED_CREATURE_P1P1 = new FilterControlledCreaturePermanent("a creature you control with a +1/+1 counter on it"); static { diff --git a/Mage/src/main/java/mage/filter/common/FilterSuspendedCard.java b/Mage/src/main/java/mage/filter/common/FilterSuspendedCard.java new file mode 100644 index 00000000000..a06ad43e909 --- /dev/null +++ b/Mage/src/main/java/mage/filter/common/FilterSuspendedCard.java @@ -0,0 +1,32 @@ +package mage.filter.common; + +import mage.abilities.keyword.SuspendAbility; +import mage.counters.CounterType; +import mage.filter.FilterCard; +import mage.filter.predicate.mageobject.AbilityPredicate; + +/** + * @author skiwkr + * 702.62b. A card is "suspended" if it's in the exile zone, has suspend, and has a time counter on it. + */ +public class FilterSuspendedCard extends FilterCard { + + public FilterSuspendedCard() { + this("suspended card"); + } + + public FilterSuspendedCard(String name) { + super(name); + this.add(new AbilityPredicate(SuspendAbility.class)); + this.add(CounterType.TIME.getPredicate()); + } + + protected FilterSuspendedCard(final FilterSuspendedCard filter) { + super(filter); + } + + @Override + public FilterSuspendedCard copy() { + return new FilterSuspendedCard(this); + } +} diff --git a/Mage/src/main/java/mage/filter/predicate/mageobject/OutlawPredicate.java b/Mage/src/main/java/mage/filter/predicate/mageobject/OutlawPredicate.java new file mode 100644 index 00000000000..40254fd5296 --- /dev/null +++ b/Mage/src/main/java/mage/filter/predicate/mageobject/OutlawPredicate.java @@ -0,0 +1,22 @@ +package mage.filter.predicate.mageobject; + +import mage.MageObject; +import mage.filter.predicate.Predicate; +import mage.game.Game; + +/** + * @author TheElk801 + */ +public enum OutlawPredicate implements Predicate { + instance; + + @Override + public boolean apply(MageObject input, Game game) { + return input.isOutlaw(game); + } + + @Override + public String toString() { + return "Outlaw"; + } +} diff --git a/Mage/src/main/java/mage/filter/predicate/mageobject/TargetsPermanentOrPlayerPredicate.java b/Mage/src/main/java/mage/filter/predicate/mageobject/TargetsPermanentOrPlayerPredicate.java new file mode 100644 index 00000000000..73a7a222840 --- /dev/null +++ b/Mage/src/main/java/mage/filter/predicate/mageobject/TargetsPermanentOrPlayerPredicate.java @@ -0,0 +1,61 @@ +package mage.filter.predicate.mageobject; + +import mage.abilities.Mode; +import mage.filter.FilterPermanent; +import mage.filter.FilterPlayer; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.stack.StackObject; +import mage.players.Player; +import mage.target.Target; + +import java.util.UUID; + +/** + * @author Susucr + */ +public class TargetsPermanentOrPlayerPredicate implements ObjectSourcePlayerPredicate { + + private final FilterPermanent targetFilterPermanent; + private final FilterPlayer targetFilterPlayer; + + public TargetsPermanentOrPlayerPredicate(FilterPermanent targetFilterPermanent, FilterPlayer targetFilterPlayer) { + this.targetFilterPermanent = targetFilterPermanent; + this.targetFilterPlayer = targetFilterPlayer; + } + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + StackObject object = game.getStack().getStackObject(input.getObject().getId()); + if (object != null) { + for (UUID modeId : object.getStackAbility().getModes().getSelectedModes()) { + Mode mode = object.getStackAbility().getModes().get(modeId); + for (Target target : mode.getTargets()) { + if (target.isNotTarget()) { + continue; + } + for (UUID targetId : target.getTargets()) { + // Try for permanent + Permanent permanent = game.getPermanent(targetId); + if (targetFilterPermanent.match(permanent, input.getPlayerId(), input.getSource(), game)) { + return true; + } + // Try for player + Player player = game.getPlayer(targetId); + if (targetFilterPlayer.match(player, input.getPlayerId(), input.getSource(), game)) { + return true; + } + } + } + } + } + return false; + } + + @Override + public String toString() { + return "that targets a " + targetFilterPermanent.getMessage() + " or " + targetFilterPlayer.getMessage(); + } +} diff --git a/Mage/src/main/java/mage/filter/predicate/mageobject/TargetsPermanentPredicate.java b/Mage/src/main/java/mage/filter/predicate/mageobject/TargetsPermanentPredicate.java index 7d2f9fe9dca..a116ab23cf1 100644 --- a/Mage/src/main/java/mage/filter/predicate/mageobject/TargetsPermanentPredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/mageobject/TargetsPermanentPredicate.java @@ -1,6 +1,5 @@ package mage.filter.predicate.mageobject; -import mage.MageObject; import mage.abilities.Mode; import mage.filter.FilterPermanent; import mage.filter.predicate.ObjectSourcePlayer; @@ -15,7 +14,7 @@ import java.util.UUID; /** * @author LoneFox */ -public class TargetsPermanentPredicate implements ObjectSourcePlayerPredicate { +public class TargetsPermanentPredicate implements ObjectSourcePlayerPredicate { private final FilterPermanent targetFilter; @@ -24,7 +23,7 @@ public class TargetsPermanentPredicate implements ObjectSourcePlayerPredicate input, Game game) { + public boolean apply(ObjectSourcePlayer input, Game game) { StackObject object = game.getStack().getStackObject(input.getObject().getId()); if (object != null) { for (UUID modeId : object.getStackAbility().getModes().getSelectedModes()) { diff --git a/Mage/src/main/java/mage/filter/predicate/mageobject/TargetsPlayerPredicate.java b/Mage/src/main/java/mage/filter/predicate/mageobject/TargetsPlayerPredicate.java index d5af2774f44..6dbe8838245 100644 --- a/Mage/src/main/java/mage/filter/predicate/mageobject/TargetsPlayerPredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/mageobject/TargetsPlayerPredicate.java @@ -1,8 +1,7 @@ package mage.filter.predicate.mageobject; -import java.util.UUID; -import mage.MageObject; import mage.abilities.Mode; +import mage.filter.FilterPlayer; import mage.filter.predicate.ObjectSourcePlayer; import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.game.Game; @@ -10,25 +9,34 @@ import mage.game.stack.StackObject; import mage.players.Player; import mage.target.Target; -/** - * - * @author jeffwadsworth - */ -public class TargetsPlayerPredicate implements ObjectSourcePlayerPredicate { +import java.util.UUID; - public TargetsPlayerPredicate() { +/** + * @author jeffwadsworth, Susucr + */ +public class TargetsPlayerPredicate implements ObjectSourcePlayerPredicate { + + private final FilterPlayer targetFilter; + + public TargetsPlayerPredicate(FilterPlayer targetFilter) { + this.targetFilter = targetFilter; } @Override - public boolean apply(ObjectSourcePlayer input, Game game) { + public boolean apply(ObjectSourcePlayer input, Game game) { StackObject object = game.getStack().getStackObject(input.getObject().getId()); if (object != null) { for (UUID modeId : object.getStackAbility().getModes().getSelectedModes()) { Mode mode = object.getStackAbility().getModes().get(modeId); for (Target target : mode.getTargets()) { + if (target.isNotTarget()) { + continue; + } for (UUID targetId : target.getTargets()) { Player player = game.getPlayer(targetId); - return player != null; + if (targetFilter.match(player, input.getPlayerId(), input.getSource(), game)) { + return true; + } } } } @@ -38,6 +46,6 @@ public class TargetsPlayerPredicate implements ObjectSourcePlayerPredicate { + instance; + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + if (input.getObject() instanceof Spell) { + return !input.getObject().isOwnedBy(input.getPlayerId()) + || !Zone.HAND.match(((Spell) input.getObject()).getFromZone()); + } else { + return !input.getObject().isOwnedBy(input.getPlayerId()) + || !Zone.HAND.match(game.getState().getZone(input.getObject().getId())); + } + } +} diff --git a/Mage/src/main/java/mage/filter/predicate/permanent/SaddledSourceThisTurnPredicate.java b/Mage/src/main/java/mage/filter/predicate/permanent/SaddledSourceThisTurnPredicate.java new file mode 100644 index 00000000000..497a70dd568 --- /dev/null +++ b/Mage/src/main/java/mage/filter/predicate/permanent/SaddledSourceThisTurnPredicate.java @@ -0,0 +1,28 @@ +package mage.filter.predicate.permanent; + +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.watchers.common.SaddledMountWatcher; + +/** + * requires SaddledMountWatcher + * + * @author TheElk801 + */ +public enum SaddledSourceThisTurnPredicate implements ObjectSourcePlayerPredicate { + instance; + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + return SaddledMountWatcher.checkIfSaddledThisTurn( + input.getObject(), input.getSource().getSourcePermanentOrLKI(game), game + ); + } + + @Override + public String toString() { + return "saddled {this} this turn"; + } +} diff --git a/Mage/src/main/java/mage/game/GameState.java b/Mage/src/main/java/mage/game/GameState.java index 21158cc1c07..ee6dd0abef7 100644 --- a/Mage/src/main/java/mage/game/GameState.java +++ b/Mage/src/main/java/mage/game/GameState.java @@ -810,67 +810,97 @@ public class GameState implements Serializable, Copyable { public void addSimultaneousDamage(DamagedEvent damagedEvent, Game game) { // Combine multiple damage events in the single event (batch) - // * per damage type (see GameEvent.DAMAGED_BATCH_FOR_PERMANENTS, GameEvent.DAMAGED_BATCH_FOR_PLAYERS) - // * per player (see GameEvent.DAMAGED_BATCH_FOR_ONE_PLAYER) - // * per permanent (see GameEvent.DAMAGED_BATCH_FOR_ONE_PERMANENT) - // - // Warning, one event can be stored in multiple batches, - // example: DAMAGED_BATCH_FOR_PLAYERS + DAMAGED_BATCH_FOR_ONE_PLAYER + // Note: one event can be stored in multiple batches + if (damagedEvent instanceof DamagedPlayerEvent) { + // DAMAGED_BATCH_FOR_PLAYERS + DAMAGED_BATCH_FOR_ONE_PLAYER + addSimultaneousDamageToPlayerBatches((DamagedPlayerEvent) damagedEvent, game); + } else if (damagedEvent instanceof DamagedPermanentEvent) { + // DAMAGED_BATCH_FOR_PERMANENTS + DAMAGED_BATCH_FOR_ONE_PERMANENT + addSimultaneousDamageToPermanentBatches((DamagedPermanentEvent) damagedEvent, game); + } + // DAMAGED_BATCH_FOR_ALL + addSimultaneousDamageToBatchForAll(damagedEvent, game); + } - boolean isPlayerDamage = damagedEvent instanceof DamagedPlayerEvent; - boolean isPermanentDamage = damagedEvent instanceof DamagedPermanentEvent; - - // existing batch - boolean isDamageBatchUsed = false; + public void addSimultaneousDamageToPlayerBatches(DamagedPlayerEvent damagedPlayerEvent, Game game) { + // find existing batches first + boolean isTotalBatchUsed = false; boolean isPlayerBatchUsed = false; - boolean isPermanentBatchUsed = false; for (GameEvent event : simultaneousEvents) { - - if (isPlayerDamage && event instanceof DamagedBatchForOnePlayerEvent) { - // per player - DamagedBatchForOnePlayerEvent oldPlayerBatch = (DamagedBatchForOnePlayerEvent) event; - if (oldPlayerBatch.getDamageClazz().isInstance(damagedEvent) - && event.getPlayerId().equals(damagedEvent.getTargetId())) { - oldPlayerBatch.addEvent(damagedEvent); - isPlayerBatchUsed = true; - } - } else if (isPermanentDamage && event instanceof DamagedBatchForOnePermanentEvent) { - // per permanent - DamagedBatchForOnePermanentEvent oldPermanentBatch = (DamagedBatchForOnePermanentEvent) event; - if (oldPermanentBatch.getDamageClazz().isInstance(damagedEvent) - && CardUtil.getEventTargets(event).contains(damagedEvent.getTargetId())) { - oldPermanentBatch.addEvent(damagedEvent); - isPermanentBatchUsed = true; - } - } else if ((event instanceof DamagedBatchEvent) - && ((DamagedBatchEvent) event).getDamageClazz().isInstance(damagedEvent)) { - // per damage type - // If the batch event isn't DAMAGED_BATCH_FOR_ONE_PLAYER, the targetIDs need not match, - // since "event" is a generic batch in this case - // (either DAMAGED_BATCH_FOR_PERMANENTS or DAMAGED_BATCH_FOR_PLAYERS) - // Just needs to be a permanent-damaging event for DAMAGED_BATCH_FOR_PERMANENTS, - // or a player-damaging event for DAMAGED_BATCH_FOR_PLAYERS - ((DamagedBatchEvent) event).addEvent(damagedEvent); - isDamageBatchUsed = true; + if (event instanceof DamagedBatchForPlayersEvent) { + ((DamagedBatchForPlayersEvent) event).addEvent(damagedPlayerEvent); + isTotalBatchUsed = true; + } else if (event instanceof DamagedBatchForOnePlayerEvent + && damagedPlayerEvent.getTargetId().equals(event.getTargetId())) { + ((DamagedBatchForOnePlayerEvent) event).addEvent(damagedPlayerEvent); + isPlayerBatchUsed = true; } - } - - // new batch - if (!isDamageBatchUsed) { - addSimultaneousEvent(DamagedBatchEvent.makeEvent(damagedEvent), game); + // new batches if necessary + if (!isTotalBatchUsed) { + addSimultaneousEvent(new DamagedBatchForPlayersEvent(damagedPlayerEvent), game); } - if (!isPlayerBatchUsed && isPlayerDamage) { - DamagedBatchEvent event = new DamagedBatchForOnePlayerEvent(damagedEvent); - addSimultaneousEvent(event, game); - } - if (!isPermanentBatchUsed && isPermanentDamage) { - DamagedBatchEvent event = new DamagedBatchForOnePermanentEvent(damagedEvent); - addSimultaneousEvent(event, game); + if (!isPlayerBatchUsed) { + addSimultaneousEvent(new DamagedBatchForOnePlayerEvent(damagedPlayerEvent), game); } } - public void addSimultaneousTapped(TappedEvent tappedEvent, Game game) { + public void addSimultaneousDamageToPermanentBatches(DamagedPermanentEvent damagedPermanentEvent, Game game) { + // find existing batches first + boolean isTotalBatchUsed = false; + boolean isSingleBatchUsed = false; + for (GameEvent event : simultaneousEvents) { + if (event instanceof DamagedBatchForPermanentsEvent) { + ((DamagedBatchForPermanentsEvent) event).addEvent(damagedPermanentEvent); + isTotalBatchUsed = true; + } else if (event instanceof DamagedBatchForOnePermanentEvent + && damagedPermanentEvent.getTargetId().equals(event.getTargetId())) { + ((DamagedBatchForOnePermanentEvent) event).addEvent(damagedPermanentEvent); + isSingleBatchUsed = true; + } + } + // new batches if necessary + if (!isTotalBatchUsed) { + addSimultaneousEvent(new DamagedBatchForPermanentsEvent(damagedPermanentEvent), game); + } + if (!isSingleBatchUsed) { + addSimultaneousEvent(new DamagedBatchForOnePermanentEvent(damagedPermanentEvent), game); + } + } + + public void addSimultaneousDamageToBatchForAll(DamagedEvent damagedEvent, Game game) { + boolean isBatchUsed = false; + for (GameEvent event : simultaneousEvents) { + if (event instanceof DamagedBatchAllEvent) { + ((DamagedBatchAllEvent) event).addEvent(damagedEvent); + isBatchUsed = true; + } + } + if (!isBatchUsed) { + addSimultaneousEvent(new DamagedBatchAllEvent(damagedEvent), game); + } + } + + public void addSimultaneousLifeLossToBatch(LifeLostEvent lifeLossEvent, Game game) { + // Combine multiple life loss events in the single event (batch) + // see GameEvent.LOST_LIFE_BATCH + + // existing batch + boolean isLifeLostBatchUsed = false; + for (GameEvent event : simultaneousEvents) { + if (event instanceof LifeLostBatchEvent) { + ((LifeLostBatchEvent) event).addEvent(lifeLossEvent); + isLifeLostBatchUsed = true; + } + } + + // new batch + if (!isLifeLostBatchUsed) { + addSimultaneousEvent(new LifeLostBatchEvent(lifeLossEvent), game); + } + } + + public void addSimultaneousTappedToBatch(TappedEvent tappedEvent, Game game) { // Combine multiple tapped events in the single event (batch) boolean isTappedBatchUsed = false; @@ -885,13 +915,11 @@ public class GameState implements Serializable, Copyable { // new batch if (!isTappedBatchUsed) { - TappedBatchEvent batch = new TappedBatchEvent(); - batch.addEvent(tappedEvent); - addSimultaneousEvent(batch, game); + addSimultaneousEvent(new TappedBatchEvent(tappedEvent), game); } } - public void addSimultaneousUntapped(UntappedEvent untappedEvent, Game game) { + public void addSimultaneousUntappedToBatch(UntappedEvent untappedEvent, Game game) { // Combine multiple untapped events in the single event (batch) boolean isUntappedBatchUsed = false; @@ -906,9 +934,7 @@ public class GameState implements Serializable, Copyable { // new batch if (!isUntappedBatchUsed) { - UntappedBatchEvent batch = new UntappedBatchEvent(); - batch.addEvent(untappedEvent); - addSimultaneousEvent(batch, game); + addSimultaneousEvent(new UntappedBatchEvent(untappedEvent), game); } } diff --git a/Mage/src/main/java/mage/game/ZonesHandler.java b/Mage/src/main/java/mage/game/ZonesHandler.java index d741e831cf9..f234e74a580 100644 --- a/Mage/src/main/java/mage/game/ZonesHandler.java +++ b/Mage/src/main/java/mage/game/ZonesHandler.java @@ -334,13 +334,19 @@ public final class ZonesHandler { isGoodToMove = true; } else if (event.getToZone().equals(Zone.BATTLEFIELD)) { // non-permanents can't move to battlefield - // "return to battlefield transformed" abilities uses game state value instead "info.transformed", so check it too - // TODO: possible bug with non permanent on second side like Life // Death, see https://github.com/magefree/mage/issues/11573 - // need to check second side here, not status only // TODO: possible bug with Nightbound, search all usage of getValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED and insert additional check Ability.checkCard - boolean wantToPutTransformed = card.isTransformable() - && Boolean.TRUE.equals(game.getState().getValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + card.getId())); - isGoodToMove = card.isPermanent(game) || wantToPutTransformed; + /* + * 712.14a. If a spell or ability puts a transforming double-faced card onto the battlefield "transformed" + * or "converted," it enters the battlefield with its back face up. If a player is instructed to put a card + * that isn't a transforming double-faced card onto the battlefield transformed or converted, that card stays in + * its current zone. + */ + boolean wantToTransform = Boolean.TRUE.equals(game.getState().getValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + card.getId())); + if (wantToTransform) { + isGoodToMove = card.isTransformable() && card.getSecondCardFace().isPermanent(game); + } else { + isGoodToMove = card.isPermanent(game); + } } else { // other zones allows to move isGoodToMove = true; diff --git a/Mage/src/main/java/mage/game/command/emblems/JayaBallardEmblem.java b/Mage/src/main/java/mage/game/command/emblems/JayaBallardEmblem.java index 6bab0d754b3..b233bfcab17 100644 --- a/Mage/src/main/java/mage/game/command/emblems/JayaBallardEmblem.java +++ b/Mage/src/main/java/mage/game/command/emblems/JayaBallardEmblem.java @@ -47,7 +47,7 @@ class JayaBallardCastFromGraveyardEffect extends AsThoughEffectImpl { JayaBallardCastFromGraveyardEffect() { super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfGame, Outcome.Benefit); - staticText = "You may cast instant and sorcery cards from your graveyard"; + staticText = "You may cast instant and sorcery spells from your graveyard"; } JayaBallardCastFromGraveyardEffect(final JayaBallardCastFromGraveyardEffect effect) { @@ -83,7 +83,7 @@ class JayaBallardReplacementEffect extends ReplacementEffectImpl { public JayaBallardReplacementEffect() { super(Duration.EndOfGame, Outcome.Exile); - staticText = "If a card cast this way would be put into a graveyard this turn, exile it instead"; + staticText = "If a spell cast this way would be put into a graveyard this turn, exile it instead"; } protected JayaBallardReplacementEffect(final JayaBallardReplacementEffect effect) { diff --git a/Mage/src/main/java/mage/game/events/BatchEvent.java b/Mage/src/main/java/mage/game/events/BatchEvent.java new file mode 100644 index 00000000000..65c005c9568 --- /dev/null +++ b/Mage/src/main/java/mage/game/events/BatchEvent.java @@ -0,0 +1,101 @@ +package mage.game.events; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * Special events created by game engine to track batches of events that occur simultaneously, + * for triggers that need such information + * @author xenohedron + */ +public abstract class BatchEvent extends GameEvent { + + private final Set events = new HashSet<>(); + private final boolean singleTargetId; + + /** + * @param eventType specific type of event + * @param singleTargetId if true, all included events must have same target id + * @param firstEvent added to initialize the batch (batch is never empty) + */ + protected BatchEvent(EventType eventType, boolean singleTargetId, T firstEvent) { + super(eventType, (singleTargetId ? firstEvent.getTargetId() : null), null, null); + this.singleTargetId = singleTargetId; + if (firstEvent instanceof BatchEvent) { // sanity check, if you need it then think twice and research carefully + throw new UnsupportedOperationException("Wrong code usage: nesting batch events not supported"); + } + this.addEvent(firstEvent); + } + + /** + * For alternate event structure logic used by ZoneChangeBatchEvent, list of events starts empty. + */ + protected BatchEvent(EventType eventType) { + super(eventType, null, null, null); + this.singleTargetId = false; + } + + public void addEvent(T event) { + if (singleTargetId && !getTargetId().equals(event.getTargetId())) { + throw new IllegalStateException("Wrong code usage. Batch event initiated with single target id, but trying to add event with different target id"); + } + this.events.add(event); + } + + public Set getEvents() { + return events; + } + + public Set getTargetIds() { + return events.stream() + .map(GameEvent::getTargetId) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + } + + public Set getSourceIds() { + return events.stream() + .map(GameEvent::getSourceId) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + } + + public Set getPlayerIds() { + return events.stream() + .map(GameEvent::getPlayerId) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + } + + @Override + public int getAmount() { + return events + .stream() + .mapToInt(GameEvent::getAmount) + .sum(); + } + + @Override // events can store a diff value, so search it from events list instead + public UUID getTargetId() { + if (singleTargetId) { + return super.getTargetId(); + } + throw new IllegalStateException("Wrong code usage. Must search value from a getEvents list or use CardUtil.getEventTargets(event)"); + } + + @Override // events can store a diff value, so search it from events list instead + @Deprecated // no use case currently supported + public UUID getSourceId() { + throw new IllegalStateException("Wrong code usage. Must search value from a getEvents list"); + } + + @Override // events can store a diff value, so search it from events list instead + @Deprecated // no use case currently supported + public UUID getPlayerId() { + throw new IllegalStateException("Wrong code usage. Must search value from a getEvents list"); + } + +} diff --git a/Mage/src/main/java/mage/game/events/BatchGameEvent.java b/Mage/src/main/java/mage/game/events/BatchGameEvent.java deleted file mode 100644 index e41e67a30bd..00000000000 --- a/Mage/src/main/java/mage/game/events/BatchGameEvent.java +++ /dev/null @@ -1,19 +0,0 @@ -package mage.game.events; - -import java.util.Set; -import java.util.UUID; - -/** - * Game event with batch support (batch is an event that can contain multiple events, example: DAMAGED_BATCH_FOR_PLAYERS) - *

- * Used by game engine to support event lifecycle for triggers - * - * @author JayDi85 - */ -public interface BatchGameEvent { - - Set getEvents(); - - Set getTargets(); - -} diff --git a/Mage/src/main/java/mage/game/events/DamagedBatchAllEvent.java b/Mage/src/main/java/mage/game/events/DamagedBatchAllEvent.java new file mode 100644 index 00000000000..f57161ea7c3 --- /dev/null +++ b/Mage/src/main/java/mage/game/events/DamagedBatchAllEvent.java @@ -0,0 +1,11 @@ +package mage.game.events; + +/** + * @author xenohedron + */ +public class DamagedBatchAllEvent extends BatchEvent { + + public DamagedBatchAllEvent(DamagedEvent firstEvent) { + super(EventType.DAMAGED_BATCH_FOR_ALL, false, firstEvent); + } +} diff --git a/Mage/src/main/java/mage/game/events/DamagedBatchEvent.java b/Mage/src/main/java/mage/game/events/DamagedBatchEvent.java deleted file mode 100644 index 5ecdee7e036..00000000000 --- a/Mage/src/main/java/mage/game/events/DamagedBatchEvent.java +++ /dev/null @@ -1,78 +0,0 @@ -package mage.game.events; - -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; - -/** - * @author TheElk801 - */ -public abstract class DamagedBatchEvent extends GameEvent implements BatchGameEvent { - - private final Class damageClazz; - private final Set events = new HashSet<>(); - - protected DamagedBatchEvent(EventType type, Class damageClazz) { - super(type, null, null, null); - this.damageClazz = damageClazz; - } - - @Override - public Set getEvents() { - return events; - } - - @Override - public Set getTargets() { - return events.stream() - .map(GameEvent::getTargetId) - .filter(Objects::nonNull) - .collect(Collectors.toSet()); - } - - @Override - public int getAmount() { - return events - .stream() - .mapToInt(GameEvent::getAmount) - .sum(); - } - - public boolean isCombatDamage() { - return events.stream().anyMatch(DamagedEvent::isCombatDamage); - } - - @Override - @Deprecated // events can store a diff value, so search it from events list instead - public UUID getTargetId() { - throw new IllegalStateException("Wrong code usage. Must search value from a getEvents list or use CardUtil.getEventTargets(event)"); - } - - @Override - @Deprecated // events can store a diff value, so search it from events list instead - public UUID getSourceId() { - throw new IllegalStateException("Wrong code usage. Must search value from a getEvents list."); - } - - public void addEvent(DamagedEvent event) { - this.events.add(event); - } - - public Class getDamageClazz() { - return damageClazz; - } - - public static DamagedBatchEvent makeEvent(DamagedEvent damagedEvent) { - DamagedBatchEvent event; - if (damagedEvent instanceof DamagedPlayerEvent) { - event = new DamagedBatchForPlayersEvent(damagedEvent); - } else if (damagedEvent instanceof DamagedPermanentEvent) { - event = new DamagedBatchForPermanentsEvent(damagedEvent); - } else { - throw new IllegalArgumentException("Wrong code usage. Unknown damage event for a new batch: " + damagedEvent.getClass().getName()); - } - return event; - } -} diff --git a/Mage/src/main/java/mage/game/events/DamagedBatchForOnePermanentEvent.java b/Mage/src/main/java/mage/game/events/DamagedBatchForOnePermanentEvent.java index 68ef57b5e06..fbf5d254095 100644 --- a/Mage/src/main/java/mage/game/events/DamagedBatchForOnePermanentEvent.java +++ b/Mage/src/main/java/mage/game/events/DamagedBatchForOnePermanentEvent.java @@ -1,10 +1,14 @@ package mage.game.events; -public class DamagedBatchForOnePermanentEvent extends DamagedBatchEvent { +public class DamagedBatchForOnePermanentEvent extends BatchEvent { - public DamagedBatchForOnePermanentEvent(DamagedEvent firstEvent) { - super(GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT, DamagedPermanentEvent.class); - addEvent(firstEvent); - setTargetId(firstEvent.getTargetId()); + public DamagedBatchForOnePermanentEvent(DamagedPermanentEvent firstEvent) { + super(GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PERMANENT, true, firstEvent); + } + + public boolean isCombatDamage() { + return getEvents() + .stream() + .anyMatch(DamagedEvent::isCombatDamage); } } diff --git a/Mage/src/main/java/mage/game/events/DamagedBatchForOnePlayerEvent.java b/Mage/src/main/java/mage/game/events/DamagedBatchForOnePlayerEvent.java index bedc02423a6..121120f804d 100644 --- a/Mage/src/main/java/mage/game/events/DamagedBatchForOnePlayerEvent.java +++ b/Mage/src/main/java/mage/game/events/DamagedBatchForOnePlayerEvent.java @@ -3,11 +3,15 @@ package mage.game.events; /** * @author Susucr */ -public class DamagedBatchForOnePlayerEvent extends DamagedBatchEvent { +public class DamagedBatchForOnePlayerEvent extends BatchEvent { - public DamagedBatchForOnePlayerEvent(DamagedEvent firstEvent) { - super(EventType.DAMAGED_BATCH_FOR_ONE_PLAYER, DamagedPlayerEvent.class); - setPlayerId(firstEvent.getPlayerId()); - addEvent(firstEvent); + public DamagedBatchForOnePlayerEvent(DamagedPlayerEvent firstEvent) { + super(EventType.DAMAGED_BATCH_FOR_ONE_PLAYER, true, firstEvent); + } + + public boolean isCombatDamage() { + return getEvents() + .stream() + .anyMatch(DamagedEvent::isCombatDamage); } } diff --git a/Mage/src/main/java/mage/game/events/DamagedBatchForPermanentsEvent.java b/Mage/src/main/java/mage/game/events/DamagedBatchForPermanentsEvent.java index aa5b2b9a6bc..4f7b1f6a776 100644 --- a/Mage/src/main/java/mage/game/events/DamagedBatchForPermanentsEvent.java +++ b/Mage/src/main/java/mage/game/events/DamagedBatchForPermanentsEvent.java @@ -3,10 +3,9 @@ package mage.game.events; /** * @author TheElk801 */ -public class DamagedBatchForPermanentsEvent extends DamagedBatchEvent { +public class DamagedBatchForPermanentsEvent extends BatchEvent { - public DamagedBatchForPermanentsEvent(DamagedEvent firstEvent) { - super(EventType.DAMAGED_BATCH_FOR_PERMANENTS, DamagedPermanentEvent.class); - addEvent(firstEvent); + public DamagedBatchForPermanentsEvent(DamagedPermanentEvent firstEvent) { + super(EventType.DAMAGED_BATCH_FOR_PERMANENTS, false, firstEvent); } } diff --git a/Mage/src/main/java/mage/game/events/DamagedBatchForPlayersEvent.java b/Mage/src/main/java/mage/game/events/DamagedBatchForPlayersEvent.java index d5fbaeea10e..172a4bcf6d1 100644 --- a/Mage/src/main/java/mage/game/events/DamagedBatchForPlayersEvent.java +++ b/Mage/src/main/java/mage/game/events/DamagedBatchForPlayersEvent.java @@ -3,10 +3,9 @@ package mage.game.events; /** * @author TheElk801 */ -public class DamagedBatchForPlayersEvent extends DamagedBatchEvent { +public class DamagedBatchForPlayersEvent extends BatchEvent { - public DamagedBatchForPlayersEvent(DamagedEvent firstEvent) { - super(GameEvent.EventType.DAMAGED_BATCH_FOR_PLAYERS, DamagedPlayerEvent.class); - addEvent(firstEvent); + public DamagedBatchForPlayersEvent(DamagedPlayerEvent firstEvent) { + super(GameEvent.EventType.DAMAGED_BATCH_FOR_PLAYERS, false, firstEvent); } } diff --git a/Mage/src/main/java/mage/game/events/DieRolledEvent.java b/Mage/src/main/java/mage/game/events/DieRolledEvent.java index d299ff4b9d5..bf8755b82a0 100644 --- a/Mage/src/main/java/mage/game/events/DieRolledEvent.java +++ b/Mage/src/main/java/mage/game/events/DieRolledEvent.java @@ -4,6 +4,8 @@ import mage.abilities.Ability; import mage.constants.PlanarDieRollResult; import mage.constants.RollDieType; +import java.util.UUID; + /** * @author TheElk801, JayDi85 */ @@ -20,8 +22,22 @@ public class DieRolledEvent extends GameEvent { private final int naturalResult; // planar die returns 0 values in result and natural result private final PlanarDieRollResult planarResult; - public DieRolledEvent(Ability source, RollDieType rollDieType, int sides, int naturalResult, int modifier, PlanarDieRollResult planarResult) { - super(EventType.DIE_ROLLED, source.getControllerId(), source, source.getControllerId(), naturalResult + modifier, false); + /** + * The target ID is used to keep track of the distinction between the player who controls the ability that + * started the dice roll and the player who does the rolling. + *

+ * The only times this distinction matters is for Chaos Dragon and Ricochet. + * + * @param source The ability causing the die roll + * @param targetId The player who rolled the die + * @param rollDieType + * @param sides + * @param naturalResult the result of the die roll before any modifiers + * @param modifier the sum of all modifiers + * @param planarResult + */ + public DieRolledEvent(Ability source, UUID targetId, RollDieType rollDieType, int sides, int naturalResult, int modifier, PlanarDieRollResult planarResult) { + super(EventType.DIE_ROLLED, targetId, source, source.getControllerId(), naturalResult + modifier, false); this.rollDieType = rollDieType; this.sides = sides; this.naturalResult = naturalResult; diff --git a/Mage/src/main/java/mage/game/events/GameEvent.java b/Mage/src/main/java/mage/game/events/GameEvent.java index 4a41084d94a..ffa1e303cdf 100644 --- a/Mage/src/main/java/mage/game/events/GameEvent.java +++ b/Mage/src/main/java/mage/game/events/GameEvent.java @@ -120,10 +120,15 @@ public class GameEvent implements Serializable { /* DAMAGED_BATCH_FOR_ONE_PLAYER combines all player damage events to a single batch (event) and split it per damaged player - playerId the id of the damaged player + targetId the id of the damaged player (playerId won't work for batch) */ DAMAGED_BATCH_FOR_ONE_PLAYER, + /* DAMAGED_BATCH_FOR_ALL + includes all damage events, both permanent damage and player damage, in single batch event + */ + DAMAGED_BATCH_FOR_ALL, + /* DAMAGE_CAUSES_LIFE_LOSS, targetId the id of the damaged player sourceId sourceId of the ability which caused the damage, can be null for default events like combat @@ -142,6 +147,10 @@ public class GameEvent implements Serializable { amount amount of life loss flag true = from combat damage - other from non combat damage */ + LOST_LIFE_BATCH, + /* LOST_LIFE_BATCH + combines all player life lost events to a single batch (event) + */ PLAY_LAND, LAND_PLAYED, CREATURE_CHAMPIONED, /* CREATURE_CHAMPIONED @@ -151,7 +160,7 @@ public class GameEvent implements Serializable { */ CREW_VEHICLE, /* CREW_VEHICLE - targetId the id of the creature that crewed a vehicle + targetId the id of the creature that will crew a vehicle sourceId sourceId of the vehicle playerId the id of the controlling player */ @@ -167,6 +176,24 @@ public class GameEvent implements Serializable { sourceId sourceId of the vehicle playerId the id of the controlling player */ + SADDLE_MOUNT, + /* SADDLE_MOUNT + targetId the id of the creature that will saddle a mount + sourceId sourceId of the mount + playerId the id of the controlling player + */ + SADDLED_MOUNT, + /* SADDLED_MOUNT + targetId the id of the creature that saddled a mount + sourceId sourceId of the mount + playerId the id of the controlling player + */ + MOUNT_SADDLED, + /* MOUNT_SADDLED + targetId the id of the mount + sourceId sourceId of the mount + playerId the id of the controlling player + */ X_MANA_ANNOUNCE, /* X_MANA_ANNOUNCE mana x-costs announced by players (X value can be changed by replace events like Unbound Flourishing) @@ -596,6 +623,12 @@ public class GameEvent implements Serializable { playerId controller of the creature mentoring */ MENTORED_CREATURE, + /* the card becomes plotted + targetId card that was plotted + sourceId of the plotting ability (may be the card itself or another one) + playerId owner of the plotted card (the one able to cast the card) + */ + BECOME_PLOTTED, //custom events CUSTOM_EVENT } diff --git a/Mage/src/main/java/mage/game/events/LifeLostBatchEvent.java b/Mage/src/main/java/mage/game/events/LifeLostBatchEvent.java new file mode 100644 index 00000000000..bed29be5456 --- /dev/null +++ b/Mage/src/main/java/mage/game/events/LifeLostBatchEvent.java @@ -0,0 +1,22 @@ +package mage.game.events; + +import java.util.UUID; + +/** + * @author jimga150 + */ +public class LifeLostBatchEvent extends BatchEvent { + + public LifeLostBatchEvent(LifeLostEvent firstEvent) { + super(EventType.LOST_LIFE_BATCH, false, firstEvent); + } + + public int getLifeLostByPlayer(UUID playerID) { + return getEvents() + .stream() + .filter(ev -> ev.getTargetId().equals(playerID)) + .mapToInt(GameEvent::getAmount) + .sum(); + } + +} diff --git a/Mage/src/main/java/mage/game/events/LifeLostEvent.java b/Mage/src/main/java/mage/game/events/LifeLostEvent.java new file mode 100644 index 00000000000..c8f5e4c860b --- /dev/null +++ b/Mage/src/main/java/mage/game/events/LifeLostEvent.java @@ -0,0 +1,16 @@ +package mage.game.events; + +import mage.abilities.Ability; + +import java.util.UUID; + +/** + * @author jimga150 + */ +public class LifeLostEvent extends GameEvent { + + public LifeLostEvent(UUID playerId, Ability source, int amount, boolean atCombat){ + super(GameEvent.EventType.LOST_LIFE, playerId, source, playerId, amount, atCombat); + } + +} diff --git a/Mage/src/main/java/mage/game/events/TappedBatchEvent.java b/Mage/src/main/java/mage/game/events/TappedBatchEvent.java index ff7a253284e..18d82863927 100644 --- a/Mage/src/main/java/mage/game/events/TappedBatchEvent.java +++ b/Mage/src/main/java/mage/game/events/TappedBatchEvent.java @@ -1,56 +1,12 @@ package mage.game.events; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; - /** * @author Susucr */ -public class TappedBatchEvent extends GameEvent implements BatchGameEvent { +public class TappedBatchEvent extends BatchEvent { - private final Set events = new HashSet<>(); - - public TappedBatchEvent() { - super(EventType.TAPPED_BATCH, null, null, null); + public TappedBatchEvent(TappedEvent firstEvent) { + super(EventType.TAPPED_BATCH, false, firstEvent); } - @Override - public Set getEvents() { - return events; - } - - @Override - public Set getTargets() { - return events.stream() - .map(GameEvent::getTargetId) - .filter(Objects::nonNull) - .collect(Collectors.toSet()); - } - - @Override - public int getAmount() { - return events - .stream() - .mapToInt(GameEvent::getAmount) - .sum(); - } - - @Override - @Deprecated // events can store a diff value, so search it from events list instead - public UUID getTargetId() { - throw new IllegalStateException("Wrong code usage. Must search value from a getEvents list or use CardUtil.getEventTargets(event)"); - } - - @Override - @Deprecated // events can store a diff value, so search it from events list instead - public UUID getSourceId() { - throw new IllegalStateException("Wrong code usage. Must search value from a getEvents list."); - } - - public void addEvent(TappedEvent event) { - this.events.add(event); - } } diff --git a/Mage/src/main/java/mage/game/events/UntappedBatchEvent.java b/Mage/src/main/java/mage/game/events/UntappedBatchEvent.java index 8fbd1f9975d..65387957ec5 100644 --- a/Mage/src/main/java/mage/game/events/UntappedBatchEvent.java +++ b/Mage/src/main/java/mage/game/events/UntappedBatchEvent.java @@ -1,56 +1,12 @@ package mage.game.events; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; - /** * @author Susucr */ -public class UntappedBatchEvent extends GameEvent implements BatchGameEvent { +public class UntappedBatchEvent extends BatchEvent { - private final Set events = new HashSet<>(); - - public UntappedBatchEvent() { - super(EventType.UNTAPPED_BATCH, null, null, null); + public UntappedBatchEvent(UntappedEvent firstEvent) { + super(EventType.UNTAPPED_BATCH, false, firstEvent); } - @Override - public Set getEvents() { - return events; - } - - @Override - public Set getTargets() { - return events.stream() - .map(GameEvent::getTargetId) - .filter(Objects::nonNull) - .collect(Collectors.toSet()); - } - - @Override - public int getAmount() { - return events - .stream() - .mapToInt(GameEvent::getAmount) - .sum(); - } - - @Override - @Deprecated // events can store a diff value, so search it from events list instead - public UUID getTargetId() { - throw new IllegalStateException("Wrong code usage. Must search value from a getEvents list or use CardUtil.getEventTargets(event)"); - } - - @Override - @Deprecated // events can store a diff value, so search it from events list instead - public UUID getSourceId() { - throw new IllegalStateException("Wrong code usage. Must search value from a getEvents list."); - } - - public void addEvent(UntappedEvent event) { - this.events.add(event); - } } diff --git a/Mage/src/main/java/mage/game/events/UntappedEvent.java b/Mage/src/main/java/mage/game/events/UntappedEvent.java index d279cde56bc..03b3dfcbc5f 100644 --- a/Mage/src/main/java/mage/game/events/UntappedEvent.java +++ b/Mage/src/main/java/mage/game/events/UntappedEvent.java @@ -6,6 +6,7 @@ import java.util.UUID; * @author Susucr */ public class UntappedEvent extends GameEvent { + public UntappedEvent(UUID targetId, UUID playerId, boolean duringUntapPhase) { super(EventType.UNTAPPED, targetId, null, playerId, 0, duringUntapPhase); } diff --git a/Mage/src/main/java/mage/game/events/ZoneChangeBatchEvent.java b/Mage/src/main/java/mage/game/events/ZoneChangeBatchEvent.java index da2b0bae48a..3e17972b2d6 100644 --- a/Mage/src/main/java/mage/game/events/ZoneChangeBatchEvent.java +++ b/Mage/src/main/java/mage/game/events/ZoneChangeBatchEvent.java @@ -1,54 +1,8 @@ package mage.game.events; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; - -public class ZoneChangeBatchEvent extends GameEvent implements BatchGameEvent { - - private final Set events = new HashSet<>(); +public class ZoneChangeBatchEvent extends BatchEvent { public ZoneChangeBatchEvent() { - super(EventType.ZONE_CHANGE_BATCH, null, null, null); - } - - @Override - public Set getEvents() { - return events; - } - - @Override - public Set getTargets() { - return events - .stream() - .map(GameEvent::getTargetId) - .filter(Objects::nonNull) - .collect(Collectors.toSet()); - } - - @Override - public int getAmount() { - return events - .stream() - .mapToInt(GameEvent::getAmount) - .sum(); - } - - @Override - @Deprecated // events can store a diff value, so search it from events list instead - public UUID getTargetId() { - throw new IllegalStateException("Wrong code usage. Must search value from a getEvents list or use CardUtil.getEventTargets(event)"); - } - - @Override - @Deprecated // events can store a diff value, so search it from events list instead - public UUID getSourceId() { - throw new IllegalStateException("Wrong code usage. Must search value from a getEvents list."); - } - - public void addEvent(ZoneChangeEvent event) { - this.events.add(event); + super(EventType.ZONE_CHANGE_BATCH); } } diff --git a/Mage/src/main/java/mage/game/permanent/Permanent.java b/Mage/src/main/java/mage/game/permanent/Permanent.java index 6ae7a036b65..ce3323d0635 100644 --- a/Mage/src/main/java/mage/game/permanent/Permanent.java +++ b/Mage/src/main/java/mage/game/permanent/Permanent.java @@ -78,6 +78,10 @@ public interface Permanent extends Card, Controllable { void setSuspected(boolean value, Game game, Ability source); + boolean isSaddled(); + + void setSaddled(boolean value); + boolean isPrototyped(); void setPrototyped(boolean value); diff --git a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java index 5d77012f736..59b53c6a5d7 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java @@ -72,6 +72,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { protected boolean monstrous; protected boolean renowned; protected boolean suspected; + protected boolean saddled; protected boolean manifested = false; protected boolean morphed = false; protected boolean disguised = false; @@ -175,6 +176,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { this.monstrous = permanent.monstrous; this.renowned = permanent.renowned; this.suspected = permanent.suspected; + this.saddled = permanent.saddled; this.ringBearerFlag = permanent.ringBearerFlag; this.classLevel = permanent.classLevel; this.goadingPlayers.addAll(permanent.goadingPlayers); @@ -203,7 +205,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { + ":" + getCardNumber() + ":" + getImageFileName() + ":" + getImageNumber(); - return name + return name + ", " + (getBasicMageObject() instanceof Token ? "T" : "C") + ", " + getBasicMageObject().getClass().getSimpleName() + ", " + imageInfo @@ -237,6 +239,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { this.maxBlockedBy = 0; this.copy = false; this.goadingPlayers.clear(); + this.saddled = false; this.loyaltyActivationsAvailable = 1; this.legendRuleApplies = true; this.canBeSacrificed = true; @@ -599,7 +602,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { game.getTurnStepType() == PhaseStep.UNTAP ); game.fireEvent(event); - game.getState().addSimultaneousUntapped(event, game); + game.getState().addSimultaneousUntappedToBatch(event, game); return true; } return false; @@ -617,7 +620,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { this.tapped = true; TappedEvent event = new TappedEvent(objectId, source, source == null ? null : source.getControllerId(), forCombat); game.fireEvent(event); - game.getState().addSimultaneousTapped(event, game); + game.getState().addSimultaneousTappedToBatch(event, game); return true; } return false; @@ -1722,6 +1725,16 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { } } + @Override + public boolean isSaddled() { + return saddled; + } + + @Override + public void setSaddled(boolean saddled) { + this.saddled = saddled; + } + // Used as key for the ring bearer info. private static final String ringbearerInfoKey = "IS_RINGBEARER"; diff --git a/Mage/src/main/java/mage/game/permanent/token/Bat21Token.java b/Mage/src/main/java/mage/game/permanent/token/Bat21Token.java new file mode 100644 index 00000000000..b9216db7755 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/Bat21Token.java @@ -0,0 +1,30 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.abilities.keyword.FlyingAbility; +import mage.constants.CardType; +import mage.constants.SubType; + +/** + * @author TheElk801 + */ +public final class Bat21Token extends TokenImpl { + + public Bat21Token() { + super("Bat Token", "2/1 black Bat creature token with flying"); + cardType.add(CardType.CREATURE); + color.setBlack(true); + subtype.add(SubType.BAT); + power = new MageInt(2); + toughness = new MageInt(1); + this.addAbility(FlyingAbility.getInstance()); + } + + private Bat21Token(final Bat21Token token) { + super(token); + } + + public Bat21Token copy() { + return new Bat21Token(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/BeauToken.java b/Mage/src/main/java/mage/game/permanent/token/BeauToken.java new file mode 100644 index 00000000000..674680aef65 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/BeauToken.java @@ -0,0 +1,41 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.common.LandsYouControlCount; +import mage.abilities.effects.common.continuous.SetBasePowerToughnessSourceEffect; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; + +/** + * @author Susucr + */ +public final class BeauToken extends TokenImpl { + + public BeauToken() { + super("Beau", "Beau, a legendary blue Ox creature token with " + + "\"This creature's power and toughness are each equal to the number of lands you control.\""); + this.cardType.add(CardType.CREATURE); + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.OX); + + this.color.setBlue(true); + + this.power = new MageInt(0); + this.toughness = new MageInt(0); + this.addAbility(new SimpleStaticAbility(new SetBasePowerToughnessSourceEffect( + LandsYouControlCount.instance, Duration.EndOfGame + ).setText("this creature's power and toughness are each equal to the number of lands you control"))); + } + + private BeauToken(final BeauToken token) { + super(token); + } + + public BeauToken copy() { + return new BeauToken(this); + } + +} diff --git a/Mage/src/main/java/mage/game/permanent/token/CatWarrior21Token.java b/Mage/src/main/java/mage/game/permanent/token/CatWarrior21Token.java new file mode 100644 index 00000000000..88e1019d1ab --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/CatWarrior21Token.java @@ -0,0 +1,30 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.constants.CardType; +import mage.constants.SubType; + +/** + * @author Susucr + */ +public final class CatWarrior21Token extends TokenImpl { + + public CatWarrior21Token() { + super("Cat Warrior Token", "2/1 white Cat Warrior creature token"); + + this.power = new MageInt(2); + this.toughness = new MageInt(1); + this.color.setWhite(true); + this.subtype.add(SubType.CAT); + this.subtype.add(SubType.WARRIOR); + this.cardType.add(CardType.CREATURE); + } + + private CatWarrior21Token(final CatWarrior21Token token) { + super(token); + } + + public CatWarrior21Token copy() { + return new CatWarrior21Token(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/DragonElementalToken.java b/Mage/src/main/java/mage/game/permanent/token/DragonElementalToken.java new file mode 100644 index 00000000000..d86acb57688 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/DragonElementalToken.java @@ -0,0 +1,33 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.ProwessAbility; +import mage.constants.CardType; +import mage.constants.SubType; + +/** + * @author TheElk801 + */ +public final class DragonElementalToken extends TokenImpl { + + public DragonElementalToken() { + super("Dragon Token", "4/4 red Dragon Elemental creature token with flying and prowess"); + cardType.add(CardType.CREATURE); + color.setRed(true); + subtype.add(SubType.DRAGON); + subtype.add(SubType.ELEMENTAL); + power = new MageInt(4); + toughness = new MageInt(4); + addAbility(FlyingAbility.getInstance()); + addAbility(new ProwessAbility()); + } + + private DragonElementalToken(final DragonElementalToken token) { + super(token); + } + + public DragonElementalToken copy() { + return new DragonElementalToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/ElkToken.java b/Mage/src/main/java/mage/game/permanent/token/ElkToken.java new file mode 100644 index 00000000000..f62f0c3fae9 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/ElkToken.java @@ -0,0 +1,28 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.constants.CardType; +import mage.constants.SubType; + +/** + * @author TheElk801 + */ +public final class ElkToken extends TokenImpl { + + public ElkToken() { + super("Elk Token", "3/3 green Elk creature token"); + cardType.add(CardType.CREATURE); + color.setGreen(true); + subtype.add(SubType.ELK); + power = new MageInt(3); + toughness = new MageInt(3); + } + + private ElkToken(final ElkToken token) { + super(token); + } + + public ElkToken copy() { + return new ElkToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/MercenaryToken.java b/Mage/src/main/java/mage/game/permanent/token/MercenaryToken.java new file mode 100644 index 00000000000..09a0e4faba4 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/MercenaryToken.java @@ -0,0 +1,39 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetControlledCreaturePermanent; + +/** + * @author TheElk801 + */ +public final class MercenaryToken extends TokenImpl { + + public MercenaryToken() { + super("Mercenary Token", "1/1 red Mercenary creature token with \"{T}: Target creature you control gets +1/+0 until end of turn. Activate only as a sorcery.\""); + cardType.add(CardType.CREATURE); + color.setRed(true); + subtype.add(SubType.MERCENARY); + power = new MageInt(1); + toughness = new MageInt(1); + + Ability ability = new ActivateAsSorceryActivatedAbility( + new BoostTargetEffect(1, 0), new TapSourceCost() + ); + ability.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(ability); + } + + private MercenaryToken(final MercenaryToken token) { + super(token); + } + + public MercenaryToken copy() { + return new MercenaryToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/MeteoriteToken.java b/Mage/src/main/java/mage/game/permanent/token/MeteoriteToken.java new file mode 100644 index 00000000000..f92d2bdb387 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/MeteoriteToken.java @@ -0,0 +1,38 @@ +package mage.game.permanent.token; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.mana.AnyColorManaAbility; +import mage.constants.CardType; +import mage.target.common.TargetAnyTarget; + +/** + * @author Susucr + */ +public final class MeteoriteToken extends TokenImpl { + + public MeteoriteToken() { + super("Meteorite", "colorless artifact token named Meteorite with " + + "\"When Meteorite enters the battlefield, it deals 2 damage to any target\" " + + "and \"{T}: Add one mana of any color.\""); + cardType.add(CardType.ARTIFACT); + + // When Meteorite enters the battlefield, it deals 2 damage to any target. + Ability ability = new EntersBattlefieldTriggeredAbility(new DamageTargetEffect(2, "it"), false); + ability.addTarget(new TargetAnyTarget()); + this.addAbility(ability); + + // {T}: Add one mana of any color. + this.addAbility(new AnyColorManaAbility()); + } + + private MeteoriteToken(final MeteoriteToken token) { + super(token); + } + + @Override + public MeteoriteToken copy() { + return new MeteoriteToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/Ox22Token.java b/Mage/src/main/java/mage/game/permanent/token/Ox22Token.java new file mode 100644 index 00000000000..9f52a5ccd33 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/Ox22Token.java @@ -0,0 +1,31 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.constants.CardType; +import mage.constants.SubType; + +/** + * @author Susucr + */ +public final class Ox22Token extends TokenImpl { + + public Ox22Token() { + super("Ox Token", "2/2 white Ox creature token"); + + cardType.add(CardType.CREATURE); + color.setWhite(true); + subtype.add(SubType.OX); + power = new MageInt(2); + toughness = new MageInt(2); + + } + + private Ox22Token(final Ox22Token token) { + super(token); + } + + @Override + public Ox22Token copy() { + return new Ox22Token(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/PlantWarriorToken.java b/Mage/src/main/java/mage/game/permanent/token/PlantWarriorToken.java new file mode 100644 index 00000000000..ba4f9eee988 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/PlantWarriorToken.java @@ -0,0 +1,32 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.abilities.keyword.ReachAbility; +import mage.constants.CardType; +import mage.constants.SubType; + +/** + * @author Susucr + */ +public final class PlantWarriorToken extends TokenImpl { + + public PlantWarriorToken() { + super("Plant Warrior Token", "4/2 green Plant Warrior creature token with reach"); + color.setGreen(true); + cardType.add(CardType.CREATURE); + subtype.add(SubType.PLANT); + subtype.add(SubType.WARRIOR); + power = new MageInt(4); + toughness = new MageInt(2); + + this.addAbility(ReachAbility.getInstance()); + } + + private PlantWarriorToken(final PlantWarriorToken token) { + super(token); + } + + public PlantWarriorToken copy() { + return new PlantWarriorToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/ScorpionDragonToken.java b/Mage/src/main/java/mage/game/permanent/token/ScorpionDragonToken.java new file mode 100644 index 00000000000..d615aa8a76b --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/ScorpionDragonToken.java @@ -0,0 +1,34 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.HasteAbility; +import mage.constants.CardType; +import mage.constants.SubType; + +/** + * @author Susucr + */ +public final class ScorpionDragonToken extends TokenImpl { + + public ScorpionDragonToken() { + super("Scorpion Dragon Token", "4/4 red Scorpion Dragon creature token with flying and haste"); + cardType.add(CardType.CREATURE); + color.setRed(true); + subtype.add(SubType.SCORPION); + subtype.add(SubType.DRAGON); + power = new MageInt(4); + toughness = new MageInt(4); + this.addAbility(FlyingAbility.getInstance()); + this.addAbility(HasteAbility.getInstance()); + } + + private ScorpionDragonToken(final ScorpionDragonToken token) { + super(token); + } + + @Override + public ScorpionDragonToken copy() { + return new ScorpionDragonToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/SheepWhiteToken.java b/Mage/src/main/java/mage/game/permanent/token/SheepWhiteToken.java new file mode 100644 index 00000000000..f897d6e6cfd --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/SheepWhiteToken.java @@ -0,0 +1,28 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.constants.CardType; +import mage.constants.SubType; + +/** + * @author TheElk801 + */ +public final class SheepWhiteToken extends TokenImpl { + + public SheepWhiteToken() { + super("Sheep Token", "1/1 white Sheep creature token"); + cardType.add(CardType.CREATURE); + color.setWhite(true); + subtype.add(SubType.SHEEP); + power = new MageInt(1); + toughness = new MageInt(1); + } + + private SheepWhiteToken(final SheepWhiteToken token) { + super(token); + } + + public SheepWhiteToken copy() { + return new SheepWhiteToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/VampireRogueToken.java b/Mage/src/main/java/mage/game/permanent/token/VampireRogueToken.java new file mode 100644 index 00000000000..c1f3120001b --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/VampireRogueToken.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 Susucr + */ +public final class VampireRogueToken extends TokenImpl { + + public VampireRogueToken() { + super("Vampire Rogue Token", "1/1 black Vampire Rogue creature token with lifelink"); + cardType.add(CardType.CREATURE); + color.setBlack(true); + subtype.add(SubType.VAMPIRE); + subtype.add(SubType.ROGUE); + power = new MageInt(1); + toughness = new MageInt(1); + this.addAbility(LifelinkAbility.getInstance()); + } + + private VampireRogueToken(final VampireRogueToken token) { + super(token); + } + + @Override + public VampireRogueToken copy() { + return new VampireRogueToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/VarmintToken.java b/Mage/src/main/java/mage/game/permanent/token/VarmintToken.java new file mode 100644 index 00000000000..d38b9163e0b --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/VarmintToken.java @@ -0,0 +1,29 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.constants.CardType; +import mage.constants.SubType; + +/** + * @author Susucr + */ +public final class VarmintToken extends TokenImpl { + + public VarmintToken() { + super("Varmint Token", "2/1 green Varmint creature token"); + cardType.add(CardType.CREATURE); + color.setGreen(true); + subtype.add(SubType.VARMINT); + power = new MageInt(2); + toughness = new MageInt(1); + } + + private VarmintToken(final VarmintToken token) { + super(token); + } + + @Override + public VarmintToken copy() { + return new VarmintToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/ZombieRogueToken.java b/Mage/src/main/java/mage/game/permanent/token/ZombieRogueToken.java new file mode 100644 index 00000000000..c063e99e670 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/ZombieRogueToken.java @@ -0,0 +1,31 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.constants.CardType; +import mage.constants.SubType; + +/** + * @author Susucr + */ +public final class ZombieRogueToken extends TokenImpl { + + public ZombieRogueToken() { + super("Zombie Rogue Token", "2/2 blue and black Zombie Rogue creature token"); + cardType.add(CardType.CREATURE); + color.setBlack(true); + color.setBlue(true); + subtype.add(SubType.ZOMBIE); + subtype.add(SubType.ROGUE); + power = new MageInt(2); + toughness = new MageInt(2); + } + + private ZombieRogueToken(final ZombieRogueToken token) { + super(token); + } + + @Override + public ZombieRogueToken copy() { + return new ZombieRogueToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/stack/StackAbility.java b/Mage/src/main/java/mage/game/stack/StackAbility.java index 722e44b704d..5348235281b 100644 --- a/Mage/src/main/java/mage/game/stack/StackAbility.java +++ b/Mage/src/main/java/mage/game/stack/StackAbility.java @@ -414,14 +414,17 @@ public class StackAbility extends StackObjectImpl implements Ability { public void addManaCostsToPay(ManaCost manaCost) { // Do nothing } + @Override public Map getCostsTagMap() { return ability.getCostsTagMap(); } + @Override - public void setCostsTag(String tag, Object value){ + public void setCostsTag(String tag, Object value) { ability.setCostsTag(tag, value); } + @Override public AbilityType getAbilityType() { return ability.getAbilityType(); @@ -539,6 +542,11 @@ public class StackAbility extends StackObjectImpl implements Ability { throw new UnsupportedOperationException("Not supported."); } + @Override + public Ability withFirstModeCost(Cost cost) { + throw new UnsupportedOperationException("Not supported."); + } + @Override public boolean activateAlternateOrAdditionalCosts(MageObject sourceObject, boolean noMana, Player controller, Game game) { throw new UnsupportedOperationException("Not supported yet."); diff --git a/Mage/src/main/java/mage/game/tournament/TournamentImpl.java b/Mage/src/main/java/mage/game/tournament/TournamentImpl.java index b7e53a86459..aee81e73498 100644 --- a/Mage/src/main/java/mage/game/tournament/TournamentImpl.java +++ b/Mage/src/main/java/mage/game/tournament/TournamentImpl.java @@ -1,40 +1,24 @@ package mage.game.tournament; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.CopyOnWriteArrayList; import mage.cards.ExpansionSet; import mage.cards.decks.Deck; import mage.constants.TournamentPlayerState; import mage.game.draft.Draft; import mage.game.draft.DraftCube; -import mage.game.events.Listener; -import mage.game.events.PlayerQueryEvent; -import mage.game.events.PlayerQueryEventSource; -import mage.game.events.TableEvent; +import mage.game.events.*; import mage.game.events.TableEvent.EventType; -import mage.game.events.TableEventSource; import mage.game.jumpstart.JumpstartPoolGenerator; import mage.game.match.Match; import mage.game.match.MatchPlayer; -import mage.game.result.ResultProtos.MatchPlayerProto; -import mage.game.result.ResultProtos.MatchProto; -import mage.game.result.ResultProtos.MatchQuitStatus; -import mage.game.result.ResultProtos.TourneyProto; -import mage.game.result.ResultProtos.TourneyRoundProto; +import mage.game.result.ResultProtos.*; import mage.players.Player; import mage.players.PlayerType; import mage.util.RandomUtil; import org.apache.log4j.Logger; +import java.util.*; +import java.util.concurrent.CopyOnWriteArrayList; + /** * * @author BetaSteward_at_googlemail.com @@ -44,7 +28,6 @@ public abstract class TournamentImpl implements Tournament { protected UUID id = UUID.randomUUID(); protected List rounds = new CopyOnWriteArrayList<>(); protected Map players = new HashMap<>(); - protected String matchName; protected TournamentOptions options; protected TournamentType tournamentType; protected List sets = new ArrayList<>(); @@ -60,7 +43,7 @@ public abstract class TournamentImpl implements Tournament { protected String tournamentState; protected Draft draft; - public TournamentImpl(TournamentOptions options) { + protected TournamentImpl(TournamentOptions options) { this.options = options; draft = null; startTime = new Date(); // will be overwritten again as the tournament really starts @@ -142,9 +125,7 @@ public abstract class TournamentImpl implements Tournament { // can only be used, if tournament did not start yet? @Override public void leave(UUID playerId) { - if (players.containsKey(playerId)) { - players.remove(playerId); - } + players.remove(playerId); } @Override @@ -306,17 +287,16 @@ public abstract class TournamentImpl implements Tournament { } } for (TournamentPlayer tp : round.getPlayerByes()) { - tp.setResults(new StringBuilder(tp.getResults()).append('R').append(round.getRoundNumber()).append(' ').append("Bye ").toString()); + tp.setResults(tp.getResults() + 'R' + round.getRoundNumber() + ' ' + "Bye "); tp.setPoints(tp.getPoints() + 3); } } } private static String addRoundResult(int round, TournamentPairing pair, TournamentPlayer tournamentPlayer, TournamentPlayer opponentPlayer) { - StringBuilder playerResult = new StringBuilder(tournamentPlayer.getResults()); - playerResult.append('R').append(round).append(' '); - playerResult.append(getMatchResultString(tournamentPlayer, opponentPlayer, pair.getMatch())); - return playerResult.toString(); + String playerResult = tournamentPlayer.getResults() + 'R' + round + ' ' + + getMatchResultString(tournamentPlayer, opponentPlayer, pair.getMatch()); + return playerResult; } private static String getMatchResultString(TournamentPlayer p1, TournamentPlayer p2, Match match) { @@ -328,13 +308,13 @@ public abstract class TournamentImpl implements Tournament { if (mp1.hasQuit()) { matchResult.append(mp1.getPlayer().hasIdleTimeout() ? "I" : (mp1.getPlayer().hasTimerTimeout() ? "T" : "Q")); } - if (match.getDraws() > 0) { - matchResult.append('-').append(match.getDraws()); - } matchResult.append('-').append(mp2.getWins()); if (mp2.hasQuit()) { matchResult.append(mp2.getPlayer().hasIdleTimeout() ? "I" : (mp2.getPlayer().hasTimerTimeout() ? "T" : "Q")); } + if (match.getDraws() > 0) { + matchResult.append('-').append(match.getDraws()); + } matchResult.append("] "); return matchResult.toString(); } diff --git a/Mage/src/main/java/mage/players/Player.java b/Mage/src/main/java/mage/players/Player.java index 5fedee1b40b..499203ff2b1 100644 --- a/Mage/src/main/java/mage/players/Player.java +++ b/Mage/src/main/java/mage/players/Player.java @@ -195,6 +195,10 @@ public interface Player extends MageItem, Copyable { boolean canPlayCardsFromGraveyard(); + void setPlotFromTopOfLibrary(boolean canPlotFromTopOfLibrary); + + boolean canPlotFromTopOfLibrary(); + void setDrawsOnOpponentsTurn(boolean drawsOnOpponentsTurn); boolean isDrawsOnOpponentsTurn(); @@ -363,6 +367,7 @@ public interface Player extends MageItem, Copyable { /** * Return player's turn control to prev player + * * @param value * @param fullRestore return turn control to own */ @@ -988,7 +993,7 @@ public interface Player extends MageItem, Copyable { * @param source * @param game * @param fromZone - * @param withName for face down: used to hide card name in game logs before real face down status apply + * @param withName for face down: used to hide card name in game logs before real face down status apply * @return */ @Deprecated diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 1fc300ac559..82eb398d3eb 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -154,6 +154,7 @@ public abstract class PlayerImpl implements Player, Serializable { protected PayLifeCostLevel payLifeCostLevel = PayLifeCostLevel.allAbilities; protected boolean loseByZeroOrLessLife = true; protected boolean canPlayCardsFromGraveyard = true; + protected boolean canPlotFromTopOfLibrary = false; protected boolean drawsOnOpponentsTurn = false; protected FilterPermanent sacrificeCostFilter; @@ -251,6 +252,7 @@ public abstract class PlayerImpl implements Player, Serializable { this.canLoseLife = player.canLoseLife; this.loseByZeroOrLessLife = player.loseByZeroOrLessLife; this.canPlayCardsFromGraveyard = player.canPlayCardsFromGraveyard; + this.canPlotFromTopOfLibrary = player.canPlotFromTopOfLibrary; this.drawsOnOpponentsTurn = player.drawsOnOpponentsTurn; this.attachments.addAll(player.attachments); @@ -360,6 +362,7 @@ public abstract class PlayerImpl implements Player, Serializable { ? player.getSacrificeCostFilter().copy() : null; this.loseByZeroOrLessLife = player.canLoseByZeroOrLessLife(); this.canPlayCardsFromGraveyard = player.canPlayCardsFromGraveyard(); + this.canPlotFromTopOfLibrary = player.canPlotFromTopOfLibrary(); this.drawsOnOpponentsTurn = player.isDrawsOnOpponentsTurn(); this.alternativeSourceCosts.clear(); this.alternativeSourceCosts.addAll(player.getAlternativeSourceCosts()); @@ -474,6 +477,7 @@ public abstract class PlayerImpl implements Player, Serializable { this.payLifeCostLevel = PayLifeCostLevel.allAbilities; this.loseByZeroOrLessLife = true; this.canPlayCardsFromGraveyard = true; + this.canPlotFromTopOfLibrary = false; this.drawsOnOpponentsTurn = false; this.sacrificeCostFilter = null; @@ -516,6 +520,7 @@ public abstract class PlayerImpl implements Player, Serializable { this.sacrificeCostFilter = null; this.loseByZeroOrLessLife = true; this.canPlayCardsFromGraveyard = false; + this.canPlotFromTopOfLibrary = false; this.drawsOnOpponentsTurn = false; this.topCardRevealed = false; this.alternativeSourceCosts.clear(); @@ -2175,8 +2180,9 @@ public abstract class PlayerImpl implements Player, Serializable { + (atCombat ? " at combat" : "") + CardUtil.getSourceLogName(game, " from ", needId, "", "")); } if (event.getAmount() > 0) { - game.fireEvent(new GameEvent(GameEvent.EventType.LOST_LIFE, - playerId, source, playerId, event.getAmount(), atCombat)); + LifeLostEvent lifeLostEvent = new LifeLostEvent(playerId, source, event.getAmount(), atCombat); + game.fireEvent(lifeLostEvent); + game.getState().addSimultaneousLifeLossToBatch(lifeLostEvent, game); } return event.getAmount(); } @@ -3292,7 +3298,7 @@ public abstract class PlayerImpl implements Player, Serializable { // raise affected roll events for (RollDieResult result : dieRolls) { - game.fireEvent(new DieRolledEvent(source, rollDiceEvent.getRollDieType(), rollDiceEvent.getSides(), result.naturalResult, result.modifier, result.planarResult)); + game.fireEvent(new DieRolledEvent(source, this.getId(), rollDiceEvent.getRollDieType(), rollDiceEvent.getSides(), result.naturalResult, result.modifier, result.planarResult)); } game.fireEvent(new DiceRolledEvent(rollDiceEvent.getSides(), dieResults, source, this.getId())); @@ -4525,6 +4531,16 @@ public abstract class PlayerImpl implements Player, Serializable { this.canPlayCardsFromGraveyard = playCardsFromGraveyard; } + @Override + public boolean canPlotFromTopOfLibrary() { + return canPlotFromTopOfLibrary; + } + + @Override + public void setPlotFromTopOfLibrary(boolean canPlotFromTopOfLibrary) { + this.canPlotFromTopOfLibrary = canPlotFromTopOfLibrary; + } + @Override public void setDrawsOnOpponentsTurn(boolean drawsOnOpponentsTurn) { this.drawsOnOpponentsTurn = drawsOnOpponentsTurn; @@ -4692,6 +4708,14 @@ public abstract class PlayerImpl implements Player, Serializable { List infoList = new ArrayList<>(); for (Card card : cards) { fromZone = game.getState().getZone(card.getId()); + // 712.14a. If a spell or ability puts a transforming double-faced card onto the battlefield "transformed" + // or "converted," it enters the battlefield with its back face up. If a player is instructed to put a card + // that isn't a transforming double-faced card onto the battlefield transformed or converted, that card stays in + // its current zone. + Boolean enterTransformed = (Boolean) game.getState().getValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + card.getId()); + if (enterTransformed != null && enterTransformed && !card.isTransformable()) { + continue; + } ZoneChangeEvent event = new ZoneChangeEvent(card.getId(), source, byOwner ? card.getOwnerId() : getId(), fromZone, Zone.BATTLEFIELD, appliedEffects); infoList.add(new ZoneChangeInfo.Battlefield(event, faceDown, tapped, source)); @@ -4703,6 +4727,7 @@ public abstract class PlayerImpl implements Player, Serializable { successfulMovedCards.add(permanent); if (!game.isSimulation()) { Player eventPlayer = game.getPlayer(info.event.getPlayerId()); + fromZone = info.event.getFromZone(); if (eventPlayer != null && fromZone != null) { game.informPlayers(eventPlayer.getLogName() + " puts " + GameLog.getColoredObjectIdName(permanent) + " from " diff --git a/Mage/src/main/java/mage/target/common/TargetCardAndOrCardInGraveyard.java b/Mage/src/main/java/mage/target/common/TargetCardAndOrCard.java similarity index 81% rename from Mage/src/main/java/mage/target/common/TargetCardAndOrCardInGraveyard.java rename to Mage/src/main/java/mage/target/common/TargetCardAndOrCard.java index fee2478e044..ddbecf60876 100644 --- a/Mage/src/main/java/mage/target/common/TargetCardAndOrCardInGraveyard.java +++ b/Mage/src/main/java/mage/target/common/TargetCardAndOrCard.java @@ -6,10 +6,13 @@ import mage.cards.Card; import mage.cards.Cards; import mage.cards.CardsImpl; import mage.constants.CardType; +import mage.constants.Zone; import mage.filter.FilterCard; import mage.filter.predicate.Predicate; import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.NamePredicate; import mage.game.Game; +import mage.target.TargetCard; import mage.util.CardUtil; import java.util.HashSet; @@ -21,7 +24,7 @@ import java.util.UUID; *

* almost identical to {@link TargetCardAndOrCardInLibrary} */ -public class TargetCardAndOrCardInGraveyard extends TargetCardInGraveyard { +public class TargetCardAndOrCard extends TargetCard { private static FilterCard makeFilter(Predicate firstPredicate, Predicate secondPredicate, @@ -42,25 +45,29 @@ public class TargetCardAndOrCardInGraveyard extends TargetCardInGraveyard { /** * a [firstType] card and/or a [secondType] card */ - protected TargetCardAndOrCardInGraveyard(Predicate firstPredicate, Predicate secondPredicate, String filterText) { - super(0, 2, makeFilter(firstPredicate, secondPredicate, filterText)); + protected TargetCardAndOrCard(Predicate firstPredicate, Predicate secondPredicate, String filterText) { + super(0, 2, Zone.ALL, makeFilter(firstPredicate, secondPredicate, filterText)); this.assignment = new PredicateCardAssignment(firstPredicate, secondPredicate); } - public TargetCardAndOrCardInGraveyard(CardType firstType, CardType secondType) { + public TargetCardAndOrCard(String firstName, String secondName) { + this(new NamePredicate(firstName), new NamePredicate(secondName), "a card named " + firstName + " and/or a card named " + secondName); + } + + public TargetCardAndOrCard(CardType firstType, CardType secondType) { this(firstType.getPredicate(), secondType.getPredicate(), makeFilterText( CardUtil.getTextWithFirstCharLowerCase(firstType.toString()), CardUtil.getTextWithFirstCharLowerCase(secondType.toString()))); } - protected TargetCardAndOrCardInGraveyard(final TargetCardAndOrCardInGraveyard target) { + protected TargetCardAndOrCard(final TargetCardAndOrCard target) { super(target); this.assignment = target.assignment; } @Override - public TargetCardAndOrCardInGraveyard copy() { - return new TargetCardAndOrCardInGraveyard(this); + public TargetCardAndOrCard copy() { + return new TargetCardAndOrCard(this); } @Override diff --git a/Mage/src/main/java/mage/target/common/TargetCardAndOrCardInLibrary.java b/Mage/src/main/java/mage/target/common/TargetCardAndOrCardInLibrary.java index 58b1338e1ba..43ac05bde77 100644 --- a/Mage/src/main/java/mage/target/common/TargetCardAndOrCardInLibrary.java +++ b/Mage/src/main/java/mage/target/common/TargetCardAndOrCardInLibrary.java @@ -10,6 +10,7 @@ import mage.constants.SubType; import mage.filter.FilterCard; import mage.filter.predicate.Predicate; import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.NamePredicate; import mage.game.Game; import mage.util.CardUtil; @@ -52,6 +53,10 @@ public class TargetCardAndOrCardInLibrary extends TargetCardInLibrary { CardUtil.getTextWithFirstCharLowerCase(secondType.toString()))); } + public TargetCardAndOrCardInLibrary(String firstName, String secondName) { + this(new NamePredicate(firstName), new NamePredicate(secondName), "a card named " + firstName + " and/or a card named " + secondName); + } + public TargetCardAndOrCardInLibrary(SubType firstType, SubType secondType) { this(firstType.getPredicate(), secondType.getPredicate(), makeFilterText(firstType.getDescription(), secondType.getDescription())); } diff --git a/Mage/src/main/java/mage/target/common/TargetNonlandPermanent.java b/Mage/src/main/java/mage/target/common/TargetNonlandPermanent.java index f88ea4463d9..b5333668777 100644 --- a/Mage/src/main/java/mage/target/common/TargetNonlandPermanent.java +++ b/Mage/src/main/java/mage/target/common/TargetNonlandPermanent.java @@ -26,7 +26,7 @@ public class TargetNonlandPermanent extends TargetPermanent { } public TargetNonlandPermanent(int minNumTargets, int maxNumTargets, boolean notTarget) { - this(minNumTargets, maxNumTargets, StaticFilters.FILTER_PERMANENT_NON_LAND, notTarget); + this(minNumTargets, maxNumTargets, (maxNumTargets > 1 ? StaticFilters.FILTER_PERMANENTS_NON_LAND : StaticFilters.FILTER_PERMANENT_NON_LAND), notTarget); } public TargetNonlandPermanent(int minNumTargets, int maxNumTargets, FilterNonlandPermanent filter, boolean notTarget) { diff --git a/Mage/src/main/java/mage/target/targetadjustment/EachOpponentPermanentTargetsAdjuster.java b/Mage/src/main/java/mage/target/targetadjustment/EachOpponentPermanentTargetsAdjuster.java new file mode 100644 index 00000000000..e4260892c34 --- /dev/null +++ b/Mage/src/main/java/mage/target/targetadjustment/EachOpponentPermanentTargetsAdjuster.java @@ -0,0 +1,48 @@ +package mage.target.targetadjustment; + +import mage.abilities.Ability; +import mage.filter.Filter; +import mage.filter.predicate.permanent.ControllerIdPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * + * @author notgreat + */ +public class EachOpponentPermanentTargetsAdjuster implements TargetAdjuster { + private final TargetPermanent blueprintTarget; + + /** + * Duplicates the permanent target for each opponent. + * Filtering of permanent's controllers will be handled inside, so + * do not pass a blueprint target with a controller restriction filter/predicate. + * + * @param blueprintTarget The target to be duplicated per opponent + */ + public EachOpponentPermanentTargetsAdjuster(TargetPermanent blueprintTarget) { + this.blueprintTarget = blueprintTarget.copy(); //Defensively copy the blueprint to ensure immutability + } + + @Override + public void adjustTargets(Ability ability, Game game) { + ability.getTargets().clear(); + for (UUID opponentId : game.getOpponents(ability.getControllerId())) { + Player opponent = game.getPlayer(opponentId); + if (opponent == null) { + continue; + } + TargetPermanent newTarget = blueprintTarget.copy(); + Filter filter = newTarget.getFilter(); + filter.add(new ControllerIdPredicate(opponentId)); + if (newTarget.canChoose(ability.getControllerId(), ability, game)) { + filter.setMessage(filter.getMessage()+" controlled by " + opponent.getLogName()); + ability.addTarget(newTarget); + } + } + } +} diff --git a/Mage/src/main/java/mage/target/targetadjustment/TargetAdjuster.java b/Mage/src/main/java/mage/target/targetadjustment/TargetAdjuster.java index 20904454459..31a3ffee340 100644 --- a/Mage/src/main/java/mage/target/targetadjustment/TargetAdjuster.java +++ b/Mage/src/main/java/mage/target/targetadjustment/TargetAdjuster.java @@ -11,5 +11,6 @@ import java.io.Serializable; @FunctionalInterface public interface TargetAdjuster extends Serializable { + // Warning: This is not Copyable, do not use changeable data inside (only use static objects like Filter) void adjustTargets(Ability ability, Game game); } diff --git a/Mage/src/main/java/mage/target/targetpointer/NthTargetPointer.java b/Mage/src/main/java/mage/target/targetpointer/NthTargetPointer.java index 88d2ea32320..0498594547c 100644 --- a/Mage/src/main/java/mage/target/targetpointer/NthTargetPointer.java +++ b/Mage/src/main/java/mage/target/targetpointer/NthTargetPointer.java @@ -21,7 +21,7 @@ public abstract class NthTargetPointer extends TargetPointerImpl { private final Map zoneChangeCounter = new HashMap<>(); private final int targetIndex; // zero-based target numbers (1 -> 0, 2 -> 1, 3 -> 2, etc) - public NthTargetPointer(int targetNumber) { + protected NthTargetPointer(int targetNumber) { super(); this.targetIndex = targetNumber - 1; } @@ -53,8 +53,9 @@ public abstract class NthTargetPointer extends TargetPointerImpl { } private void wrongTargetsUsage(Ability source) { - if (this.targetIndex > 0) { + if (this.targetIndex > 0 && source.getTargetAdjuster() == null) { // first target pointer is default, so must be ignored + // also legitimate use case with target adjuster e.g. Jilt throw new IllegalStateException("Wrong code usage: source ability miss targets setup for target pointer - " + this.getClass().getSimpleName() + " - " + source.getClass().getSimpleName() + " - " + source); } diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java index fe884e5b028..97820c04195 100644 --- a/Mage/src/main/java/mage/util/CardUtil.java +++ b/Mage/src/main/java/mage/util/CardUtil.java @@ -30,7 +30,7 @@ import mage.game.CardState; import mage.game.Game; import mage.game.GameState; import mage.game.command.Commander; -import mage.game.events.BatchGameEvent; +import mage.game.events.BatchEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.permanent.PermanentCard; @@ -1280,6 +1280,7 @@ public final class CardUtil { } } + // TODO: use CastManaAdjustment instead of boolean anyColor public static void makeCardPlayable(Game game, Ability source, Card card, Duration duration, boolean anyColor) { makeCardPlayable(game, source, card, duration, anyColor, null, null); } @@ -1296,6 +1297,7 @@ public final class CardUtil { * @param anyColor * @param condition can be null */ + // TODO: use CastManaAdjustment instead of boolean anyColor public static void makeCardPlayable(Game game, Ability source, Card card, Duration duration, boolean anyColor, UUID playerId, Condition condition) { // Effect can be used for cards in zones and permanents on battlefield // PermanentCard's ZCC is static, but we need updated ZCC from the card (after moved to another zone) @@ -1323,7 +1325,7 @@ public final class CardUtil { * such as the adventure and main side of adventure spells or both sides of a fuse card. * * @param cardToCast - * @param filter A filter to determine if a card is eligible for casting. + * @param filter An optional filter to determine if a card is eligible for casting. * @param source The ability or source responsible for the casting. * @param player * @param game @@ -1347,7 +1349,9 @@ public final class CardUtil { if (!playLand || !player.canPlayLand() || !game.isActivePlayer(playerId)) { cards.removeIf(card -> card.isLand(game)); } - cards.removeIf(card -> !filter.match(card, playerId, source, game)); + if (filter != null) { + cards.removeIf(card -> !filter.match(card, playerId, source, game)); + } if (spellCastTracker != null) { cards.removeIf(card -> !spellCastTracker.checkCard(card, game)); } @@ -2203,14 +2207,11 @@ public final class CardUtil { /** * One single event can be a batch (contain multiple events) - * - * @param event - * @return */ public static Set getEventTargets(GameEvent event) { Set res = new HashSet<>(); - if (event instanceof BatchGameEvent) { - res.addAll(((BatchGameEvent) event).getTargets()); + if (event instanceof BatchEvent) { + res.addAll(((BatchEvent) event).getTargetIds()); } else if (event != null && event.getTargetId() != null) { res.add(event.getTargetId()); } diff --git a/Mage/src/main/java/mage/watchers/common/CommittedCrimeWatcher.java b/Mage/src/main/java/mage/watchers/common/CommittedCrimeWatcher.java new file mode 100644 index 00000000000..f8bb6038160 --- /dev/null +++ b/Mage/src/main/java/mage/watchers/common/CommittedCrimeWatcher.java @@ -0,0 +1,53 @@ +package mage.watchers.common; + +import mage.abilities.Ability; +import mage.abilities.common.CommittedCrimeTriggeredAbility; +import mage.constants.WatcherScope; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.watchers.Watcher; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public class CommittedCrimeWatcher extends Watcher { + + // players who committed a crime this turn + private final Set criminals = new HashSet<>(); + + public CommittedCrimeWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + switch (event.getType()) { + case SPELL_CAST: + case ACTIVATED_ABILITY: + case TRIGGERED_ABILITY: + break; + default: + return; + } + Optional.ofNullable(CommittedCrimeTriggeredAbility.getCriminal(event, game)).ifPresent(criminals::add); + } + + @Override + public void reset() { + super.reset(); + criminals.clear(); + } + + public static boolean checkCriminality(Game game, Ability source) { + return game + .getState() + .getWatcher(CommittedCrimeWatcher.class) + .criminals + .contains(source.getControllerId()); + } +} diff --git a/Mage/src/main/java/mage/watchers/common/CrewedVehicleWatcher.java b/Mage/src/main/java/mage/watchers/common/CrewedVehicleWatcher.java index 1b43c367e35..677e5ec0015 100644 --- a/Mage/src/main/java/mage/watchers/common/CrewedVehicleWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/CrewedVehicleWatcher.java @@ -43,4 +43,13 @@ public class CrewedVehicleWatcher extends Watcher { .stream() .anyMatch(mor -> mor.refersTo(crewer, game)); } + + public static int getCrewCount(Permanent vehicle, Game game) { + return game + .getState() + .getWatcher(CrewedVehicleWatcher.class) + .crewMap + .getOrDefault(new MageObjectReference(vehicle, game), Collections.emptySet()) + .size(); + } } diff --git a/Mage/src/main/java/mage/watchers/common/OnceEachTurnCastWatcher.java b/Mage/src/main/java/mage/watchers/common/OnceEachTurnCastWatcher.java new file mode 100644 index 00000000000..2ac2b578add --- /dev/null +++ b/Mage/src/main/java/mage/watchers/common/OnceEachTurnCastWatcher.java @@ -0,0 +1,77 @@ +package mage.watchers.common; + +import mage.MageIdentifier; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.hint.Hint; +import mage.constants.WatcherScope; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.players.Player; +import mage.watchers.Watcher; + +import java.util.*; + +/** + * @author xenohedron + */ +public class OnceEachTurnCastWatcher extends Watcher { + + private final Map> usedFrom = new HashMap<>(); + + /** + * For abilities that permit the casting of a spell from not own hand zone once each turn (per player) + */ + public OnceEachTurnCastWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() == GameEvent.EventType.SPELL_CAST + && event.getPlayerId() != null + && event.hasApprovingIdentifier(MageIdentifier.OnceEachTurnCastWatcher)) { + usedFrom.computeIfAbsent(event.getPlayerId(), k -> new HashSet<>()) + .add(event.getAdditionalReference().getApprovingMageObjectReference()); + } + } + + @Override + public void reset() { + super.reset(); + usedFrom.clear(); + } + + public boolean isAbilityUsed(UUID playerId, MageObjectReference mor) { + return usedFrom.getOrDefault(playerId, Collections.emptySet()).contains(mor); + } + + public static Hint getHint() { + return OnceEachTurnCastHint.instance; + } + +} + +enum OnceEachTurnCastHint implements Hint { + instance; + + @Override + public String getText(Game game, Ability ability) { + OnceEachTurnCastWatcher watcher = game.getState().getWatcher(OnceEachTurnCastWatcher.class); + if (watcher != null) { + boolean used = watcher.isAbilityUsed(ability.getControllerId(), new MageObjectReference(ability.getSourceId(), game)); + if (used) { + Player player = game.getPlayer(ability.getControllerId()); + if (player != null) { + return "A spell has been cast by " + player.getLogName() + " with {this} this turn."; + } + } + } + return ""; + } + + @Override + public OnceEachTurnCastHint copy() { + return this; + } +} diff --git a/Mage/src/main/java/mage/watchers/common/PermanentsEnteredBattlefieldWatcher.java b/Mage/src/main/java/mage/watchers/common/PermanentsEnteredBattlefieldWatcher.java index 0ea9bbc3206..ba83140c7e1 100644 --- a/Mage/src/main/java/mage/watchers/common/PermanentsEnteredBattlefieldWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/PermanentsEnteredBattlefieldWatcher.java @@ -50,7 +50,7 @@ public class PermanentsEnteredBattlefieldWatcher extends Watcher { } public List getThisTurnEnteringPermanents(UUID playerId) { - return enteringBattlefield.get(playerId); + return enteringBattlefield.getOrDefault(playerId, Collections.emptyList()); } public boolean anotherCreatureEnteredBattlefieldUnderPlayersControlLastTurn(Permanent sourcePermanent, Game game) { diff --git a/Mage/src/main/java/mage/watchers/common/PlanarRollWatcher.java b/Mage/src/main/java/mage/watchers/common/PlanarRollWatcher.java index 9bd1b184a93..691087ad770 100644 --- a/Mage/src/main/java/mage/watchers/common/PlanarRollWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/PlanarRollWatcher.java @@ -29,7 +29,7 @@ public class PlanarRollWatcher extends Watcher { public void watch(GameEvent event, Game game) { if (event.getType() == GameEvent.EventType.DIE_ROLLED) { DieRolledEvent drEvent = (DieRolledEvent) event; - UUID playerId = drEvent.getPlayerId(); + UUID playerId = drEvent.getTargetId(); if (playerId != null && drEvent.getRollDieType() == RollDieType.PLANAR) { Integer amount = numberTimesPlanarDieRolled.get(playerId); if (amount == null) { diff --git a/Mage/src/main/java/mage/watchers/common/SaddledMountWatcher.java b/Mage/src/main/java/mage/watchers/common/SaddledMountWatcher.java new file mode 100644 index 00000000000..cbc6c0859df --- /dev/null +++ b/Mage/src/main/java/mage/watchers/common/SaddledMountWatcher.java @@ -0,0 +1,56 @@ +package mage.watchers.common; + +import mage.MageObjectReference; +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.*; + +/** + * @author TheElk801 + */ +public class SaddledMountWatcher extends Watcher { + + // key: the mount, value: set of creatures which saddled + private final Map> saddleMap = new HashMap<>(); + + public SaddledMountWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() == GameEvent.EventType.SADDLED_MOUNT) { + saddleMap.computeIfAbsent(new MageObjectReference(event.getSourceId(), game), x -> new HashSet<>()) + .add(new MageObjectReference(event.getTargetId(), game)); + } + } + + @Override + public void reset() { + super.reset(); + saddleMap.clear(); + } + + public static boolean checkIfSaddledThisTurn(Permanent saddler, Permanent mount, Game game) { + return game + .getState() + .getWatcher(SaddledMountWatcher.class) + .saddleMap + .getOrDefault(new MageObjectReference(mount, game), Collections.emptySet()) + .stream() + .anyMatch(mor -> mor.refersTo(saddler, game)); + } + + public static int getSaddleCount(Permanent vehicle, Game game) { + return game + .getState() + .getWatcher(SaddledMountWatcher.class) + .saddleMap + .getOrDefault(new MageObjectReference(vehicle, game), Collections.emptySet()) + .size(); + } +} diff --git a/Utils/keywords.txt b/Utils/keywords.txt index 02f7e59008a..ee44b28dc67 100644 --- a/Utils/keywords.txt +++ b/Utils/keywords.txt @@ -95,6 +95,7 @@ Persist|new| Phasing|instance| Plainscycling|cost| Plainswalk|new| +Plot|manaString| Poisonous|number| Prototype|card, manaString| Provoke|new| @@ -106,6 +107,7 @@ Reconfigure|manaString| Renown|number| Replicate|manaString| Riot|new| +Saddle|number| Scavenge|cost| Shadow|instance| Shroud|instance| @@ -113,7 +115,8 @@ Soulbond|new| Soulshift|number| Skulk|new| Spectacle|card, cost| -Squad|new| +Spree|card| +Squad|cost| Storm|new| Sunburst|new| Suspend|number, cost, card| diff --git a/Utils/known-sets.txt b/Utils/known-sets.txt index ef5c12414ea..0abf9fe4da2 100644 --- a/Utils/known-sets.txt +++ b/Utils/known-sets.txt @@ -195,6 +195,7 @@ Oath of the Gatewatch|OathOfTheGatewatch| Odyssey|Odyssey| Onslaught|Onslaught| Outlaws of Thunder Junction|OutlawsOfThunderJunction| +Outlaws of Thunder Junction Commander|OutlawsOfThunderJunctionCommander| The Big Score|TheBigScore| Phyrexia: All Will Be One|PhyrexiaAllWillBeOne| Phyrexia: All Will Be One Commander|PhyrexiaAllWillBeOneCommander| diff --git a/Utils/mtg-cards-data.txt b/Utils/mtg-cards-data.txt index 0395064a917..ed67b5cb789 100644 --- a/Utils/mtg-cards-data.txt +++ b/Utils/mtg-cards-data.txt @@ -52392,10 +52392,277 @@ Knowledge Is Power|Murders at Karlov Manor Commander|42|R|{3}{W}{U}|Enchantment| Take the Bait|Murders at Karlov Manor Commander|43|R|{2}{R}{W}|Instant|||Cast this spell only during an opponent's turn and only during combat.$Prevent all combat damage that would be dealt to you and planeswalkers you control this turn. Untap all attacking creatures and goad them. After this phase, there is an additional combat phase.| Panoptic Projektor|Murders at Karlov Manor Commander|44|R|{4}|Artifact|||{T}: The next face-down creature spell you cast this turn costs {3} less to cast.$If turning a face-down permanent face up causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time.| Ransom Note|Murders at Karlov Manor Commander|45|R|{1}|Artifact - Clue|||When Ransom Note enters the battlefield, surveil 1.${2}, Sacrifice Ransom Note: Choose one --$* Cloak the top card of your library.$* Goad target creature.$* Draw a card.| +Another Round|Outlaws of Thunder Junction|1|R|{X}{X}{2}{W}|Sorcery|||Exile any number of creatures you control, then return them to the battlefield under their owner's control. Then repeat this process X more times.| +Archangel of Tithes|Outlaws of Thunder Junction|2|M|{1}{W}{W}{W}|Creature - Angel|3|5|Flying$As long as Archangel of Tithes is untapped, creatures can't attack you or planeswalkers you control unless their controller pays {1} for each of those creatures.$As long as Archangel of Tithes is attacking, creatures can't block unless their controller pays {1} for each of those creatures.| +Armored Armadillo|Outlaws of Thunder Junction|3|C|{W}|Creature - Armadillo|0|4|Ward {1}${3}{W}: Armored Armadillo gets +X/+0 until end of turn, where X is its toughness.| +Aven Interrupter|Outlaws of Thunder Junction|4|R|{1}{W}{W}|Creature - Bird Rogue|2|2|Flash$Flying$When Aven Interrupter enters the battlefield, exile target spell. It becomes plotted.$Spells your opponents cast from graveyards or from exile cost {2} more to cast.| +Bounding Felidar|Outlaws of Thunder Junction|5|U|{5}{W}|Creature - Cat Beast Mount|4|7|Whenever Bounding Felidar attacks while saddled, put a +1/+1 counter on each other creature you control. You gain 1 life for each of those creatures.$Saddle 2| +Bovine Intervention|Outlaws of Thunder Junction|6|U|{1}{W}|Instant|||Destroy target artifact or creature. Its controller creates a 2/2 white Ox creature token.| +Bridled Bighorn|Outlaws of Thunder Junction|7|C|{3}{W}|Creature - Sheep Mount|3|4|Vigilance$Whenever Bridled Bighorn attacks while saddled, create a 1/1 white Sheep creature token.$Saddle 2| +Claim Jumper|Outlaws of Thunder Junction|8|R|{2}{W}|Creature - Rabbit Mercenary|3|3|Vigilance$When Claim Jumper enters the battlefield, if an opponent controls more lands than you, you may search your library for a Plains card and put it onto the battlefield tapped. Then if an opponent controls more lands than you, repeat this process once. If you search your library this way, shuffle.| +Dust Animus|Outlaws of Thunder Junction|9|R|{1}{W}|Creature - Spirit|2|3|Flying$If you control five or more untapped lands, Dust Animus enters the battlefield with two +1/+1 counters and a lifelink counter on it.$Plot {1}{W}| +Eriette's Lullaby|Outlaws of Thunder Junction|10|C|{1}{W}|Sorcery|||Destroy target tapped creature. You gain 2 life.| +Final Showdown|Outlaws of Thunder Junction|11|M|{W}|Instant|||Spree$+ {1} -- All creatures lose all abilities until end of turn.$+ {1} -- Choose a creature you control. It gains indestructible until end of turn.$+ {3}{W}{W} -- Destroy all creatures.| +Fortune, Loyal Steed|Outlaws of Thunder Junction|12|R|{2}{W}|Legendary Creature - Beast Mount|2|4|When Fortune, Loyal Steed enters the battlefield, scry 2.$Whenever Fortune attacks while saddled, at end of combat, exile it and up to one creature that saddled it this turn, then return those cards to the battlefield under their owner's control.$Saddle 1| +Frontier Seeker|Outlaws of Thunder Junction|13|U|{1}{W}|Creature - Human Scout|2|1|When Frontier Seeker enters the battlefield, look at the top five cards of your library. You may reveal a Mount creature card or a Plains card from among them and put it into your hand. Put the rest on the bottom of your library in a random order.| +Getaway Glamer|Outlaws of Thunder Junction|14|U|{W}|Instant|||Spree$+ {1} -- Exile target nontoken creature. Return it to the battlefield under its owner's control at the beginning of the next end step.$+ {2} -- Destroy target creature if no other creature has greater power.| +High Noon|Outlaws of Thunder Junction|15|R|{1}{W}|Enchantment|||Each player can't cast more than one spell each turn.${4}{R}, Sacrifice High Noon: It deals 5 damage to any target.| +Holy Cow|Outlaws of Thunder Junction|16|C|{2}{W}|Creature - Ox Angel|2|2|Flash$Flying$When Holy Cow enters the battlefield, you gain 2 life and scry 1.| +Inventive Wingsmith|Outlaws of Thunder Junction|17|C|{2}{W}|Creature - Dwarf Artificer|2|4|At the beginning of your end step, if you haven't cast a spell from your hand this turn and Inventive Wingsmith doesn't have a flying counter on it, put a flying counter on it.| +Lassoed by the Law|Outlaws of Thunder Junction|18|U|{3}{W}|Enchantment|||When Lassoed by the Law enters the battlefield, exile target nonland permanent an opponent controls until Lassoed by the Law leaves the battlefield.$When Lassoed by the Law enters the battlefield, create a 1/1 red Mercenary creature token with "{T}: Target creature you control gets +1/+0 until end of turn. Activate only as a sorcery."| +Mystical Tether|Outlaws of Thunder Junction|19|C|{2}{W}|Enchantment|||You may cast Mystical Tether as though it had flash if you pay {2} more to cast it.$When Mystical Tether enters the battlefield, exile target artifact or creature an opponent controls until Mystical Tether leaves the battlefield.| +Nurturing Pixie|Outlaws of Thunder Junction|20|U|{W}|Creature - Faerie Rogue|1|1|Flying$When Nurturing Pixie enters the battlefield, return up to one target non-Faerie, nonland permanent you control to its owner's hand. If a permanent was returned this way, put a +1/+1 counter on Nurturing Pixie.| +Omenport Vigilante|Outlaws of Thunder Junction|21|U|{1}{W}|Creature - Human Mercenary|2|2|Omenport Vigilante has double strike as long as you've committed a crime this turn.| +One Last Job|Outlaws of Thunder Junction|22|R|{2}{W}|Sorcery|||Spree$+ {2} -- Return target creature card from your graveyard to the battlefield.$+ {1} -- Return target Mount or Vehicle card from your graveyard to the battlefield.$+ {1} -- Return target Aura or Equipment card from your graveyard to the battlefield attached to a creature you control.| +Outlaw Medic|Outlaws of Thunder Junction|23|C|{1}{W}|Creature - Human Rogue|1|3|Lifelink$When Outlaw Medic dies, draw a card.| +Prairie Dog|Outlaws of Thunder Junction|24|U|{1}{W}|Creature - Squirrel|2|2|Lifelink$At the beginning of your end step, if you haven't cast a spell from your hand this turn, put a +1/+1 counter on Prairie Dog.${4}{W}: Until end of turn, if you would put one or more +1/+1 counters on a creature you control, put that many plus one +1/+1 counters on it instead.| +Prosperity Tycoon|Outlaws of Thunder Junction|25|U|{3}{W}|Creature - Human Noble|4|2|When Prosperity Tycoon enters the battlefield, create a 1/1 red Mercenary creature token with "{T}: Target creature you control gets +1/+0 until end of turn. Activate only as a sorcery."${2}, Sacrifice a token: Prosperity Tycoon gains indestructible until end of turn. Tap it.| +Requisition Raid|Outlaws of Thunder Junction|26|U|{W}|Sorcery|||Spree$+ {1} -- Destroy target artifact.$+ {1} -- Destroy target enchantment.$+ {1} -- Put a +1/+1 counter on each creature target player controls.| +Rustler Rampage|Outlaws of Thunder Junction|27|U|{W}|Instant|||Spree$+ {1} -- Untap all creatures target player controls.$+ {1} -- Target creature gains double strike until end of turn.| +Shepherd of the Clouds|Outlaws of Thunder Junction|28|U|{4}{W}|Creature - Pegasus|4|3|Flying, vigilance$When Shepherd of the Clouds enters the battlefield, return target permanent card with mana value 3 or less from your graveyard to your hand. Return that card to the battlefield instead if you control a Mount.| +Sheriff of Safe Passage|Outlaws of Thunder Junction|29|U|{2}{W}|Creature - Human Knight|0|0|Sheriff of Safe Passage enters the battlefield with a +1/+1 counter on it plus an additional +1/+1 counter on it for each other creature you control.$Plot {1}{W}| +Stagecoach Security|Outlaws of Thunder Junction|30|C|{4}{W}|Creature - Human Soldier|4|5|When Stagecoach Security enters the battlefield, creatures you control get +1/+1 and gain vigilance until end of turn.$Plot {3}{W}| +Steer Clear|Outlaws of Thunder Junction|31|C|{W}|Instant|||Steer Clear deals 2 damage to target attacking or blocking creature. Steer Clear deals 4 damage to that creature instead if you controlled a Mount as you cast this spell.| +Sterling Keykeeper|Outlaws of Thunder Junction|32|C|{1}{W}|Creature - Human Mercenary|2|2|{2}, {T}: Tap target non-Mount creature.| +Sterling Supplier|Outlaws of Thunder Junction|33|C|{4}{W}|Creature - Bird Soldier|3|4|Flying$When Sterling Supplier enters the battlefield, put a +1/+1 counter on another target creature you control.| +Take Up the Shield|Outlaws of Thunder Junction|34|C|{1}{W}|Instant|||Put a +1/+1 counter on target creature. It gains lifelink and indestructible until end of turn.| +Thunder Lasso|Outlaws of Thunder Junction|35|U|{2}{W}|Artifact - Equipment|||When Thunder Lasso enters the battlefield, attach it to target creature you control.$Equipped creature gets +1/+1.$Whenever equipped creature attacks, tap target creature defending player controls.$Equip {2}| +Trained Arynx|Outlaws of Thunder Junction|36|C|{1}{W}|Creature - Cat Beast Mount|3|1|Whenever Trained Arynx attacks while saddled, it gains first strike until end of turn. Scry 1.$Saddle 2| +Vengeful Townsfolk|Outlaws of Thunder Junction|37|C|{2}{W}|Creature - Human Citizen|3|3|Whenever one or more other creatures you control die, put a +1/+1 counter on Vengeful Townsfolk.| +Wanted Griffin|Outlaws of Thunder Junction|38|C|{3}{W}|Creature - Griffin|3|2|Flying$When Wanted Griffin dies, create a 1/1 red Mercenary creature token with "{T}: Target creature you control gets +1/+0 until end of turn. Activate only as a sorcery."| +Archmage's Newt|Outlaws of Thunder Junction|39|R|{1}{U}|Creature - Salamander Mount|2|2|Whenever Archmage's Newt deals combat damage to a player, target instant or sorcery card in your graveyard gains flashback until end of turn. The flashback cost is equal to its mana cost. That card gains flashback {0} until end of turn instead if Archmage's Newt is saddled.$Saddle 3| +Canyon Crab|Outlaws of Thunder Junction|40|U|{1}{U}|Creature - Crab|0|5|{1}{U}: Canyon Crab gets +2/-2 until end of turn.$At the beginning of your end step, if you haven't cast a spell from your hand this turn, draw a card, then discard a card.| +Daring Thunder-Thief|Outlaws of Thunder Junction|41|C|{3}{U}|Creature - Turtle Rogue|4|4|Flash$Daring Thunder-Thief enters the battlefield tapped.| +Deepmuck Desperado|Outlaws of Thunder Junction|42|U|{2}{U}|Creature - Homarid Mercenary|2|4|Whenever you commit a crime, each opponent mills three cards. This ability triggers only once each turn.| +Djinn of Fool's Fall|Outlaws of Thunder Junction|43|C|{4}{U}|Creature - Djinn|4|3|Flying$Plot {3}{U}| +Double Down|Outlaws of Thunder Junction|44|M|{3}{U}|Enchantment|||Whenever you cast an outlaw spell, copy that spell.| +Duelist of the Mind|Outlaws of Thunder Junction|45|R|{1}{U}|Creature - Human Advisor|*|3|Flying, vigilance$Duelist of the Mind's power is equal to the number of cards you've drawn this turn.$Whenever you commit a crime, you may draw a card. If you do, discard a card. This ability triggers only once each turn.| +Emergent Haunting|Outlaws of Thunder Junction|46|U|{1}{U}|Enchantment|||At the beginning of your end step, if you haven't cast a spell from your hand this turn and Emergent Haunting isn't a creature, it becomes a 3/3 Spirit creature with flying in addition to its other types.${2}{U}: Surveil 1.| +Failed Fording|Outlaws of Thunder Junction|47|C|{1}{U}|Instant|||Return target nonland permanent to its owner's hand. If you control a Desert, surveil 1.| Fblthp, Lost on the Range|Outlaws of Thunder Junction|48|R|{1}{U}{U}|Legendary Creature - Homunculus|1|1|Ward {2}$You may look at the top card of your library any time.$The top card of your library has plot. The plot cost is equal to its mana cost.$You may plot nonland cards from the top of your library.| +Fleeting Reflection|Outlaws of Thunder Junction|49|U|{1}{U}|Instant|||Target creature you control gains hexproof until end of turn. Untap that creature. Until end of turn, it becomes a copy of up to one other target creature.| +Geralf, the Fleshwright|Outlaws of Thunder Junction|50|M|{2}{U}|Legendary Creature - Human Warlock|2|3|Whenever you cast a spell during your turn other than your first spell that turn, create a 2/2 blue and black Zombie Rogue creature token.$Whenever a Zombie enters the battlefield under your control, put a +1/+1 counter on it for each other Zombie that entered the battlefield under your control this turn.| +Geyser Drake|Outlaws of Thunder Junction|51|C|{2}{U}|Creature - Drake|2|3|Flying$As long as it's not your turn, spells you cast cost {1} less to cast.| +Harrier Strix|Outlaws of Thunder Junction|52|C|{U}|Creature - Bird|1|1|Flying$When Harrier Strix enters the battlefield, tap target permanent.${2}{U}: Draw a card, then discard a card.| +Jailbreak Scheme|Outlaws of Thunder Junction|53|C|{U}|Sorcery|||Spree$+ {3} -- Put a +1/+1 counter on target creature. It can't be blocked this turn.$+ {2} -- Target artifact or creature's owner puts it on the top or bottom of their library.| +The Key to the Vault|Outlaws of Thunder Junction|54|R|{1}{U}|Legendary Artifact - Equipment|||Whenever equipped creature deals combat damage to a player, look at that many cards from the top of your library. You may exile a nonland card from among them. Put the rest on the bottom of your library in a random order. You may cast the exiled card without paying its mana cost.$Equip {2}{U}| +Loan Shark|Outlaws of Thunder Junction|55|C|{3}{U}|Creature - Shark Rogue|3|4|When Loan Shark enters the battlefield, if you've cast two or more spells this turn, draw a card.$Plot {3}{U}| +Marauding Sphinx|Outlaws of Thunder Junction|56|U|{3}{U}{U}|Creature - Sphinx Rogue|3|5|Flying, vigilance, ward {2}$Whenever you commit a crime, surveil 2. This ability triggers only once each turn.| +Metamorphic Blast|Outlaws of Thunder Junction|57|U|{U}|Instant|||Spree$+ {1} -- Until end of turn, target creature becomes a white Rabbit with base power and toughness 0/1.$+ {3} -- Target player draws two cards.| +Nimble Brigand|Outlaws of Thunder Junction|58|U|{2}{U}|Creature - Human Rogue|1|3|Nimble Brigand can't be blocked if you've committed a crime this turn.$Whenever Nimble Brigand deals combat damage to a player, draw a card.| +Outlaw Stitcher|Outlaws of Thunder Junction|59|U|{3}{U}|Creature - Human Warlock|1|4|When Outlaw Stitcher enters the battlefield, create a 2/2 blue and black Zombie Rogue creature token, then put two +1/+1 counters on that token for each spell you've cast this turn other than the first.$Plot {4}{U}| +Peerless Ropemaster|Outlaws of Thunder Junction|60|C|{4}{U}|Creature - Human Rogue|4|4|When Peerless Ropemaster enters the battlefield, return up to one target tapped creature to its owner's hand.| +Phantom Interference|Outlaws of Thunder Junction|61|C|{U}|Instant|||Spree$+ {3} -- Create a 2/2 white Spirit creature token with flying.$+ {1} -- Counter target spell unless its controller pays {2}.| +Plan the Heist|Outlaws of Thunder Junction|62|U|{2}{U}{U}|Sorcery|||Surveil 3 if you have no cards in hand. Then draw three cards.$Plot {3}{U}| +Razzle-Dazzler|Outlaws of Thunder Junction|63|C|{1}{U}|Creature - Human Wizard|1|2|Whenever you cast your second spell each turn, put a +1/+1 counter on Razzle-Dazzler. It can't be blocked this turn.| +Seize the Secrets|Outlaws of Thunder Junction|64|C|{2}{U}|Sorcery|||This spell costs {1} less to cast if you've committed a crime this turn.$Draw two cards.| +Shackle Slinger|Outlaws of Thunder Junction|65|U|{2}{U}|Creature - Human Soldier|3|2|Whenever you cast your second spell each turn, choose target creature an opponent controls. If it's tapped, put a stun counter on it. Otherwise, tap it.| +Shifting Grift|Outlaws of Thunder Junction|66|U|{U}{U}|Sorcery|||Spree$+ {2} -- Exchange control of two target creatures.$+ {1} -- Exchange control of two target artifacts.$+ {1} -- Exchange control of two target enchantments.| +Slickshot Lockpicker|Outlaws of Thunder Junction|67|U|{2}{U}|Creature - Human Rogue|2|3|When Slickshot Lockpicker enters the battlefield, target instant or sorcery card in your graveyard gains flashback until end of turn. The flashback cost is equal to its mana cost.$Plot {2}{U}| +Slickshot Vault-Buster|Outlaws of Thunder Junction|68|C|{2}{U}|Creature - Human Rogue|1|4|Vigilance$Slickshot Vault-Buster gets +2/+0 as long as you've committed a crime this turn.| +Spring Splasher|Outlaws of Thunder Junction|69|C|{1}{U}|Creature - Frog Beast|2|1|Whenever Spring Splasher attacks, target creature defending player controls gets -3/-0 until end of turn.| +Step Between Worlds|Outlaws of Thunder Junction|70|R|{3}{U}{U}|Sorcery|||Each player may shuffle their hand and graveyard into their library. Each player who does draws seven cards. Exile Step Between Worlds.$Plot {4}{U}{U}| +Stoic Sphinx|Outlaws of Thunder Junction|71|R|{2}{U}{U}|Creature - Sphinx|5|3|Flash$Flying$Stoic Sphinx has hexproof as long as you haven't cast a spell this turn.| +Stop Cold|Outlaws of Thunder Junction|72|C|{3}{U}|Enchantment - Aura|||Flash$Enchant artifact or creature$When Stop Cold enters the battlefield, tap enchanted permanent.$Enchanted permanent loses all abilities and doesn't untap during its controller's untap step.| +Take the Fall|Outlaws of Thunder Junction|73|C|{U}|Instant|||Target creature gets -1/-0 until end of turn. It gets -4/-0 until end of turn instead if you control an outlaw.$Draw a card.| +This Town Ain't Big Enough|Outlaws of Thunder Junction|74|U|{4}{U}|Instant|||This spell costs {3} less to cast if it targets a permanent you control.$Return up to two target nonland permanents to their owners' hands.| +Three Steps Ahead|Outlaws of Thunder Junction|75|R|{U}|Instant|||Spree$+ {1}{U} -- Counter target spell.$+ {3} -- Create a token that's a copy of target artifact or creature you control.$+ {2} -- Draw two cards, then discard a card.| +Visage Bandit|Outlaws of Thunder Junction|76|U|{3}{U}|Creature - Shapeshifter Rogue|2|2|You may have Visage Bandit enter the battlefield as a copy of a creature you control, except it's a Shapeshifter Rogue in addition to its other types.$Plot {2}{U}| +Ambush Gigapede|Outlaws of Thunder Junction|77|C|{4}{B}{B}|Creature - Insect|6|2|Flash$When Ambush Gigapede enters the battlefield, target creature an opponent controls gets -2/-2 until end of turn.| +Binding Negotiation|Outlaws of Thunder Junction|78|U|{1}{B}|Sorcery|||Target opponent reveals their hand. You may choose a nonland card from it. If you do, they discard it. Otherwise, you may put a face-up exiled card they own into their graveyard.| +Blacksnag Buzzard|Outlaws of Thunder Junction|79|C|{2}{B}|Creature - Bird|2|1|Flying$Blacksnag Buzzard enters the battlefield with a +1/+1 counter on it if a creature died this turn.$Plot {1}{B}| +Blood Hustler|Outlaws of Thunder Junction|80|U|{1}{B}|Creature - Vampire Rogue|1|1|Whenever you commit a crime, put a +1/+1 counter on Blood Hustler. This ability triggers only once each turn.${3}{B}: Target opponent loses 1 life and you gain 1 life.| +Boneyard Desecrator|Outlaws of Thunder Junction|81|C|{3}{B}|Creature - Zombie Mercenary|3|4|Menace${1}{B}, Sacrifice another creature: Put a +1/+1 counter on Boneyard Desecrator. If an outlaw was sacrificed this way, create a Treasure token.| +Caustic Bronco|Outlaws of Thunder Junction|82|R|{1}{B}|Creature - Snake Horse Mount|2|2|Whenever Caustic Bronco attacks, reveal the top card of your library and put it into your hand. You lose life equal to that card's mana value if Caustic Bronco isn't saddled. Otherwise, each opponent loses that much life.$Saddle 3| +Consuming Ashes|Outlaws of Thunder Junction|83|C|{2}{B}{B}|Instant|||Exile target creature. If it had mana value 3 or less, surveil 2.| +Corrupted Conviction|Outlaws of Thunder Junction|84|C|{B}|Instant|||As an additional cost to cast this spell, sacrifice a creature.$Draw two cards.| +Desert's Due|Outlaws of Thunder Junction|85|C|{1}{B}|Instant|||Target creature gets -2/-2 until end of turn. It gets an additional -1/-1 until end of turn for each Desert you control.| +Desperate Bloodseeker|Outlaws of Thunder Junction|86|C|{1}{B}|Creature - Vampire|2|2|Lifelink$When Desperate Bloodseeker enters the battlefield, target player mills two cards.| +Fake Your Own Death|Outlaws of Thunder Junction|87|C|{1}{B}|Instant|||Until end of turn, target creature gets +2/+0 and gains "When this creature dies, return it to the battlefield tapped under its owner's control and you create a Treasure token."| +Forsaken Miner|Outlaws of Thunder Junction|88|U|{B}|Creature - Skeleton Rogue|2|2|Forsaken Miner can't block.$Whenever you commit a crime, you may pay {B}. If you do, return Forsaken Miner from your graveyard to the battlefield.| +Gisa, the Hellraiser|Outlaws of Thunder Junction|89|M|{3}{B}{B}|Legendary Creature - Human Warlock|4|4|Ward--{2}, Pay 2 life.$Skeletons and Zombies you control get +1/+1 and have menace.$Whenever you commit a crime, create two tapped 2/2 blue and black Zombie Rogue creature tokens. This ability triggers only once each turn.| +Hollow Marauder|Outlaws of Thunder Junction|90|U|{6}{B}|Creature - Specter Rogue|4|2|This spell costs {1} less to cast for each creature card in your graveyard.$Flying$When Hollow Marauder enters the battlefield, any number of target opponents each discard a card. For each of those opponents who didn't discard a card with mana value 4 or greater, draw a card.| +Insatiable Avarice|Outlaws of Thunder Junction|91|R|{B}|Sorcery|||Spree$+ {2} -- Search your library for a card, then shuffle and put that card on top.$+ {B}{B} -- Target player draws three cards and loses 3 life.| +Kaervek, the Punisher|Outlaws of Thunder Junction|92|R|{1}{B}{B}|Legendary Creature - Human Warlock|3|3|Whenever you commit a crime, exile up to one target black card from your graveyard and copy it. You may cast the copy. If you do, you lose 2 life.| +Lively Dirge|Outlaws of Thunder Junction|93|U|{1}{B}|Sorcery|||Spree$+ {1} -- Search your library for a card, put it into your graveyard, then shuffle.$+ {2} -- Return up to two creature cards with total mana value 4 or less from your graveyard to the battlefield.| +Mourner's Surprise|Outlaws of Thunder Junction|94|C|{1}{B}|Sorcery|||Return up to one target creature card from your graveyard to your hand. Create a 1/1 red Mercenary creature token with "{T}: Target creature you control gets +1/+0 until end of turn. Activate only as a sorcery."| +Neutralize the Guards|Outlaws of Thunder Junction|95|U|{2}{B}|Instant|||Creatures target opponent controls get -1/-1 until end of turn. Surveil 2.| +Nezumi Linkbreaker|Outlaws of Thunder Junction|96|C|{B}|Creature - Rat Warlock|1|1|When Nezumi Linkbreaker dies, create a 1/1 red Mercenary creature token with "{T}: Target creature you control gets +1/+0 until end of turn. Activate only as a sorcery."| +Overzealous Muscle|Outlaws of Thunder Junction|97|C|{4}{B}|Creature - Ogre Mercenary|5|4|Whenever you commit a crime during your turn, Overzealous Muscle gains indestructible until end of turn.| +Pitiless Carnage|Outlaws of Thunder Junction|98|R|{3}{B}|Sorcery|||Sacrifice any number of permanents you control, then draw that many cards.$Plot {1}{B}{B}| +Rakish Crew|Outlaws of Thunder Junction|99|U|{2}{B}|Enchantment|||When Rakish Crew enters the battlefield, create a 1/1 red Mercenary creature token with "{T}: Target creature you control gets +1/+0 until end of turn. Activate only as a sorcery."$Whenever an outlaw you control dies, each opponent loses 1 life and you gain 1 life.| +Rattleback Apothecary|Outlaws of Thunder Junction|100|U|{2}{B}|Creature - Gorgon Warlock|3|2|Deathtouch$Whenever you commit a crime, target creature you control gains your choice of menace or lifelink until end of turn.| +Raven of Fell Omens|Outlaws of Thunder Junction|101|C|{1}{B}|Creature - Bird|1|2|Flying$Whenever you commit a crime, each opponent loses 1 life and you gain 1 life. This ability triggers only once each turn.| +Rictus Robber|Outlaws of Thunder Junction|102|U|{3}{B}|Creature - Zombie Rogue|4|3|When Rictus Robber enters the battlefield, if a creature died this turn, create a 2/2 blue and black Zombie Rogue creature token.$Plot {2}{B}| +Rooftop Assassin|Outlaws of Thunder Junction|103|C|{3}{B}|Creature - Vampire Assassin|2|2|Flash$Flying, lifelink$When Rooftop Assassin enters the battlefield, destroy target creature an opponent controls that was dealt damage this turn.| +Rush of Dread|Outlaws of Thunder Junction|104|R|{1}{B}{B}|Sorcery|||Spree$+ {1} -- Target opponent sacrifices half the creatures they control, rounded up.$+ {2} -- Target opponent discards half the cards in their hand, rounded up.$+ {2} -- Target opponent loses half their life, rounded up.| +Servant of the Stinger|Outlaws of Thunder Junction|105|U|{1}{B}|Creature - Human Warlock|1|3|Deathtouch$Whenever Servant of the Stinger deals combat damage to a player, if you've committed a crime this turn, you may sacrifice Servant of the Stinger. If you do, search your library for a card, put it into your hand, then shuffle.| +Shoot the Sheriff|Outlaws of Thunder Junction|106|U|{1}{B}|Instant|||Destroy target non-outlaw creature.| +Skulduggery|Outlaws of Thunder Junction|107|C|{B}|Instant|||Until end of turn, target creature you control gets +1/+1 and target creature an opponent controls gets -1/-1.| +Tinybones Joins Up|Outlaws of Thunder Junction|108|R|{B}|Legendary Enchantment|||When Tinybones Joins Up enters the battlefield, any number of target players each discard a card.$Whenever a legendary creature enters the battlefield under your control, any number of target players each mill a card and lose 1 life.| Tinybones, the Pickpocket|Outlaws of Thunder Junction|109|M|{B}|Legendary Creature - Skeleton Rogue|1|1|Deathtouch$Whenever Tinybones, the Pickpocket deals combat damage to a player, you may cast target nonland permanent card from that player's graveyard, and mana of any type can be spent to cast that spell.| +Treasure Dredger|Outlaws of Thunder Junction|110|U|{1}{B}|Creature - Human Rogue|2|2|{1}, {T}, Pay 1 life: Create a Treasure token.| +Unfortunate Accident|Outlaws of Thunder Junction|111|U|{B}|Instant|||Spree$+ {2}{B} -- Destroy target creature.$+ {1} -- Create a 1/1 red Mercenary creature token with "{T}: Target creature you control gets +1/+0 until end of turn. Activate only as a sorcery."| +Unscrupulous Contractor|Outlaws of Thunder Junction|112|U|{2}{B}|Creature - Human Assassin|3|2|When Unscrupulous Contractor enters the battlefield, you may sacrifice a creature. When you do, target player draws two cards and loses 2 life.$Plot {2}{B}| +Vadmir, New Blood|Outlaws of Thunder Junction|113|R|{1}{B}|Legendary Creature - Vampire Rogue|2|2|Whenever you commit a crime, put a +1/+1 counter on Vadmir, New Blood. This ability triggers only once each turn.$As long as Vadmir has four or more +1/+1 counters on it, it has menace and lifelink.| +Vault Plunderer|Outlaws of Thunder Junction|114|C|{2}{B}|Creature - Human Rogue|3|1|When Vault Plunderer enters the battlefield, target player draws a card and loses 1 life.| +Brimstone Roundup|Outlaws of Thunder Junction|115|U|{1}{R}|Enchantment|||Whenever you cast your second spell each turn, create a 1/1 red Mercenary creature token with "{T}: Target creature you control gets +1/+0 until end of turn. Activate only as a sorcery."$Plot {2}{R}| +Calamity, Galloping Inferno|Outlaws of Thunder Junction|116|R|{4}{R}{R}|Legendary Creature - Horse Mount|4|6|Haste$Whenever Calamity, Galloping Inferno attacks while saddled, choose a nonlegendary creature that saddled it this turn and create a tapped and attacking token that's a copy of it. Sacrifice that token at the beginning of the next end step. Repeat this process once.$Saddle 1| +Caught in the Crossfire|Outlaws of Thunder Junction|117|U|{R}{R}|Instant|||Spree$+ {1} -- Caught in the Crossfire deals 2 damage to each outlaw creature.$+ {1} -- Caught in the Crossfire deals 2 damage to each non-outlaw creature.| +Cunning Coyote|Outlaws of Thunder Junction|118|U|{1}{R}|Creature - Coyote|2|2|Haste$When Cunning Coyote enters the battlefield, another target creature you control gets +1/+1 and gains haste until end of turn.$Plot {1}{R}| +Deadeye Duelist|Outlaws of Thunder Junction|119|C|{1}{R}|Creature - Human Assassin|1|3|Reach${1}, {T}: Deadeye Duelist deals 1 damage to target opponent.| +Demonic Ruckus|Outlaws of Thunder Junction|120|U|{1}{R}|Enchantment - Aura|||Enchant creature$Enchanted creature gets +1/+1 and has menace and trample.$When Demonic Ruckus is put into a graveyard from the battlefield, draw a card.$Plot {R}| +Discerning Peddler|Outlaws of Thunder Junction|121|C|{1}{R}|Creature - Human Rogue|2|2|When Discerning Peddler enters the battlefield, you may discard a card. If you do, draw a card.| +Explosive Derailment|Outlaws of Thunder Junction|122|C|{R}|Instant|||Spree$+ {2} -- Explosive Derailment deals 4 damage to target creature.$+ {2} -- Destroy target artifact.| +Ferocification|Outlaws of Thunder Junction|123|U|{2}{R}|Enchantment|||At the beginning of combat on your turn, choose one --$* Target creature you control gets +2/+0 until end of turn.$* Target creature you control gains menace and haste until end of turn.| +Gila Courser|Outlaws of Thunder Junction|124|U|{2}{R}|Creature - Lizard Mount|4|2|Whenever Gila Courser attacks while saddled, exile the top card of your library. Until the end of your next turn, you may play that card.$Saddle 1| +Great Train Heist|Outlaws of Thunder Junction|125|R|{R}|Instant|||Spree$+ {2}{R} -- Untap all creatures you control. If it's your combat phase, there is an additional combat phase after this phase.$+ {2} -- Creatures you control get +1/+0 and gain first strike until end of turn.$+ {R} -- Choose target opponent. Whenever a creature you control deals combat damage to that player this turn, create a tapped Treasure token.| Hell to Pay|Outlaws of Thunder Junction|126|R|{X}{R}|Sorcery|||Hell to Pay deals X damage to target creature. Create a number of tapped Treasure tokens equal to the amount of excess damage dealt to that creature this way.| +Hellspur Brute|Outlaws of Thunder Junction|127|U|{4}{R}|Creature - Minotaur Mercenary|5|4|Affinity for outlaws$Trample| +Hellspur Posse Boss|Outlaws of Thunder Junction|128|R|{2}{R}{R}|Creature - Lizard Rogue|2|4|Other outlaws you control have haste.$When Hellspur Posse Boss enters the battlefield, create two 1/1 red Mercenary creature tokens with "{T}: Target creature you control gets +1/+0 until end of turn. Activate only as a sorcery."| +Highway Robbery|Outlaws of Thunder Junction|129|C|{1}{R}|Sorcery|||You may discard a card or sacrifice a land. If you do, draw two cards.$Plot {1}{R}| +Irascible Wolverine|Outlaws of Thunder Junction|130|C|{2}{R}|Creature - Wolverine|3|2|When Irascible Wolverine enters the battlefield, exile the top card of your library. Until end of turn, you may play that card.$Plot {2}{R}| +Iron-Fist Pulverizer|Outlaws of Thunder Junction|131|C|{4}{R}|Creature - Giant Warrior|4|5|Reach$Whenever you cast your second spell each turn, Iron-Fist Pulverizer deals 2 damage to target opponent. Scry 1.| +Longhorn Sharpshooter|Outlaws of Thunder Junction|132|U|{2}{R}|Creature - Minotaur Rogue|3|3|Reach$When Longhorn Sharpshooter becomes plotted, it deals 2 damage to any target.$Plot {3}{R}| +Magda, the Hoardmaster|Outlaws of Thunder Junction|133|R|{1}{R}|Legendary Creature - Dwarf Berserker|2|2|Whenever you commit a crime, create a tapped Treasure token. This ability triggers only once each turn.$Sacrifice three Treasures: Create a 4/4 red Scorpion Dragon creature token with flying and haste. Activate only as a sorcery.| +Magebane Lizard|Outlaws of Thunder Junction|134|U|{1}{R}|Creature - Lizard|1|4|Whenever a player casts a noncreature spell, Magebane Lizard deals damage to that player equal to the number of noncreature spells they've cast this turn.| +Mine Raider|Outlaws of Thunder Junction|135|C|{2}{R}|Creature - Human Rogue|3|2|Trample$When Mine Raider enters the battlefield, if you control another outlaw, create a Treasure token.| +Outlaws' Fury|Outlaws of Thunder Junction|136|C|{2}{R}|Instant|||Creatures you control get +2/+0 until end of turn. If you control an outlaw, exile the top card of your library. Until the end of your next turn, you may play that card.| +Prickly Pair|Outlaws of Thunder Junction|137|C|{2}{R}|Creature - Plant Mercenary|2|2|When Prickly Pair enters the battlefield, create a 1/1 red Mercenary creature token with "{T}: Target creature you control gets +1/+0 until end of turn. Activate only as a sorcery."| +Quick Draw|Outlaws of Thunder Junction|138|C|{R}|Instant|||Target creature you control gets +1/+1 and gains first strike until end of turn. Creatures target opponent controls lose first strike and double strike until end of turn.| +Quilled Charger|Outlaws of Thunder Junction|139|C|{3}{R}|Creature - Porcupine Mount|4|3|Whenever Quilled Charger attacks while saddled, it gets +1/+2 and gains menace until end of turn.$Saddle 2| +Reckless Lackey|Outlaws of Thunder Junction|140|C|{R}|Creature - Goblin Pirate|1|2|First strike, haste${2}{R}, Sacrifice Reckless Lackey: Draw a card and create a Treasure token.| +Resilient Roadrunner|Outlaws of Thunder Junction|141|U|{1}{R}|Creature - Bird|2|2|Haste, protection from Coyotes${3}: Resilient Roadrunner can't be blocked this turn except by creatures with haste.| +Return the Favor|Outlaws of Thunder Junction|142|U|{R}{R}|Instant|||Spree$+ {1} -- Copy target instant spell, sorcery spell, activated ability, or triggered ability. You may choose new targets for the copy.$+ {1} -- Change the target of target spell or ability with a single target.| +Rodeo Pyromancers|Outlaws of Thunder Junction|143|C|{3}{R}|Creature - Human Mercenary|3|4|Whenever you cast your first spell each turn, add {R}{R}.| +Scalestorm Summoner|Outlaws of Thunder Junction|144|U|{2}{R}|Creature - Human Warlock|3|3|Whenever Scalestorm Summoner attacks, create a 3/1 red Dinosaur creature token if you control a creature with power 4 or greater.| +Scorching Shot|Outlaws of Thunder Junction|145|U|{R}{R}|Sorcery|||Scorching Shot deals 5 damage to target creature.| +Slickshot Show-Off|Outlaws of Thunder Junction|146|R|{1}{R}|Creature - Bird Wizard|1|2|Flying, haste$Whenever you cast a noncreature spell, Slickshot Show-Off gets +2/+0 until end of turn.$Plot {1}{R}| +Stingerback Terror|Outlaws of Thunder Junction|147|R|{2}{R}{R}|Creature - Scorpion Dragon|7|7|Flying, trample$Stingerback Terror gets -1/-1 for each card in your hand.$Plot {2}{R}| +Take for a Ride|Outlaws of Thunder Junction|148|U|{2}{R}|Sorcery|||Take for a Ride has flash as long as you've committed a crime this turn.$Gain control of target creature until end of turn. Untap that creature. It gains haste until end of turn.| +Terror of the Peaks|Outlaws of Thunder Junction|149|M|{3}{R}{R}|Creature - Dragon|5|4|Flying$Spells your opponents cast that target Terror of the Peaks cost an additional 3 life to cast.$Whenever another creature enters the battlefield under your control, Terror of the Peaks deals damage equal to that creature's power to any target.| +Thunder Salvo|Outlaws of Thunder Junction|150|C|{1}{R}|Instant|||Thunder Salvo deals X damage to target creature, where X is 2 plus the number of other spells you've cast this turn.| +Trick Shot|Outlaws of Thunder Junction|151|C|{4}{R}|Instant|||Trick Shot deals 6 damage to target creature and 2 damage to up to one other target creature token.| +Aloe Alchemist|Outlaws of Thunder Junction|152|U|{1}{G}|Creature - Plant Warlock|3|2|Trample$When Aloe Alchemist becomes plotted, target creature gets +3/+2 and gains trample until end of turn.$Plot {1}{G}| +Ankle Biter|Outlaws of Thunder Junction|153|C|{G}|Creature - Snake|1|1|Deathtouch| +Beastbond Outcaster|Outlaws of Thunder Junction|154|U|{2}{G}|Creature - Human Druid|3|3|When Beastbond Outcaster enters the battlefield, if you control a creature with power 4 or greater, draw a card.$Plot {1}{G}| +Betrayal at the Vault|Outlaws of Thunder Junction|155|U|{4}{G}{G}|Instant|||Target creature you control deals damage equal to its power to each of two other target creatures.| +Bristlepack Sentry|Outlaws of Thunder Junction|156|C|{1}{G}|Creature - Plant Wolf|3|3|Defender$As long as you control a creature with power 4 or greater, Bristlepack Sentry can attack as though it didn't have defender.| +Bristly Bill, Spine Sower|Outlaws of Thunder Junction|157|M|{1}{G}|Legendary Creature - Plant Druid|2|2|Landfall -- Whenever a land enters the battlefield under your control, put a +1/+1 counter on target creature.${3}{G}{G}: Double the number of +1/+1 counters on each creature you control.| +Cactarantula|Outlaws of Thunder Junction|158|C|{4}{G}{G}|Creature - Plant Spider|6|5|This spell costs {1} less to cast if you control a Desert.$Reach$Whenever Cactarantula becomes the target of a spell or ability an opponent controls, you may draw a card.| +Colossal Rattlewurm|Outlaws of Thunder Junction|159|R|{2}{G}{G}|Creature - Wurm|6|5|Colossal Rattlewurm has flash as long as you control a Desert.$Trample${1}{G}, Exile Colossal Rattlewurm from your graveyard: Search your library for a Desert card, put it onto the battlefield tapped, then shuffle.| +Dance of the Tumbleweeds|Outlaws of Thunder Junction|160|C|{1}{G}|Sorcery|||Spree$+ {1} -- Search your library for a basic land card or a Desert card, put it onto the battlefield, then shuffle.$+ {3} -- Create an X/X green Elemental creature token, where X is the number of lands you control.| +Drover Grizzly|Outlaws of Thunder Junction|161|C|{2}{G}|Creature - Bear Mount|4|2|Whenever Drover Grizzly attacks while saddled, creatures you control gain trample until end of turn.$Saddle 1| +Freestrider Commando|Outlaws of Thunder Junction|162|C|{2}{G}|Creature - Centaur Mercenary|3|3|Freestrider Commando enters the battlefield with two +1/+1 counters on it if it wasn't cast or no mana was spent to cast it.$Plot {3}{G}| +Freestrider Lookout|Outlaws of Thunder Junction|163|R|{2}{G}|Creature - Human Rogue|3|3|Reach$Whenever you commit a crime, look at the top five cards of your library. You may put a land card from among them onto the battlefield tapped. Put the rest on the bottom of your library in a random order. This ability triggers only once each turn.| +Full Steam Ahead|Outlaws of Thunder Junction|164|U|{3}{G}{G}|Sorcery|||Until end of turn, each creature you control gets +2/+2 and gains trample and "This creature can't be blocked by more than one creature."| +Giant Beaver|Outlaws of Thunder Junction|165|C|{3}{G}|Creature - Beaver Mount|4|4|Vigilance$Whenever Giant Beaver attacks while saddled, put a +1/+1 counter on target creature that saddled it this turn.$Saddle 3| +Gold Rush|Outlaws of Thunder Junction|166|U|{1}{G}|Instant|||Create a Treasure token. Until end of turn, up to one target creature gets +2/+2 for each Treasure you control.| +Goldvein Hydra|Outlaws of Thunder Junction|167|M|{X}{G}|Creature - Hydra|0|0|Vigilance, trample, haste$Goldvein Hydra enters the battlefield with X +1/+1 counters on it.$When Goldvein Hydra dies, create a number of tapped Treasure tokens equal to its power.| +Hardbristle Bandit|Outlaws of Thunder Junction|168|C|{1}{G}|Creature - Plant Rogue|1|1|{T}: Add one mana of any color.$Whenever you commit a crime, untap Hardbristle Bandit. This ability triggers only once each turn.| +Intrepid Stablemaster|Outlaws of Thunder Junction|169|U|{1}{G}|Creature - Human Scout|2|2|Reach${T}: Add {G}.${T}: Add two mana of any one color. Spend this mana only to cast Mount or Vehicle spells.| +Map the Frontier|Outlaws of Thunder Junction|170|U|{3}{G}|Sorcery|||Search your library for up to two basic land cards and/or Desert cards, put them onto the battlefield tapped, then shuffle.| +Ornery Tumblewagg|Outlaws of Thunder Junction|171|R|{2}{G}|Creature - Brushwagg Mount|2|2|At the beginning of combat on your turn, put a +1/+1 counter on target creature.$Whenever Ornery Tumblewagg attacks while saddled, double the number of +1/+1 counters on target creature.$Saddle 2| +Outcaster Greenblade|Outlaws of Thunder Junction|172|U|{2}{G}|Creature - Human Mercenary|1|2|When Outcaster Greenblade enters the battlefield, search your library for a basic land card or a Desert card, reveal it, put it into your hand, then shuffle.$Outcaster Greenblade gets +1/+1 for each Desert you control.| +Outcaster Trailblazer|Outlaws of Thunder Junction|173|R|{2}{G}|Creature - Human Druid|4|2|When Outcaster Trailblazer enters the battlefield, add one mana of any color.$Whenever another creature with power 4 or greater enters the battlefield under your control, draw a card.$Plot {2}{G}| +Patient Naturalist|Outlaws of Thunder Junction|174|C|{2}{G}|Creature - Human Scout|2|3|When Patient Naturalist enters the battlefield, mill three cards. Put a land card from among the milled cards into your hand. If you can't, create a Treasure token.| +Railway Brawler|Outlaws of Thunder Junction|175|M|{3}{G}{G}|Creature - Rhino Warrior|5|5|Reach, trample$Whenever another creature enters the battlefield under your control, put X +1/+1 counters on it, where X is its power.$Plot {3}{G}| +Rambling Possum|Outlaws of Thunder Junction|176|U|{2}{G}|Creature - Possum Mount|3|3|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| +Raucous Entertainer|Outlaws of Thunder Junction|177|U|{1}{G}|Creature - Plant Bard|2|2|{1}, {T}: Put a +1/+1 counter on each creature you control that entered the battlefield this turn.| +Reach for the Sky|Outlaws of Thunder Junction|178|C|{3}{G}|Enchantment - Aura|||Flash$Enchant creature$Enchanted creature gets +3/+2 and has reach.$When Reach for the Sky is put into a graveyard from the battlefield, draw a card.| +Rise of the Varmints|Outlaws of Thunder Junction|179|U|{3}{G}|Sorcery|||Create X 2/1 green Varmint creature tokens, where X is the number of creature cards in your graveyard.$Plot {2}{G}| +Smuggler's Surprise|Outlaws of Thunder Junction|180|R|{G}|Instant|||Spree$+ {2} -- Mill four cards. You may put up to two creature and/or land cards from among the milled cards into your hand.$+ {4}{G} -- You may put up to two creature cards from your hand onto the battlefield.$+ {1} -- Creatures you control with power 4 or greater gain hexproof and indestructible until end of turn.| +Snakeskin Veil|Outlaws of Thunder Junction|181|C|{G}|Instant|||Put a +1/+1 counter on target creature you control. It gains hexproof until end of turn.| +Spinewoods Armadillo|Outlaws of Thunder Junction|182|U|{4}{G}{G}|Creature - Armadillo|7|7|Reach$Ward {3}${1}{G}, Discard Spinewoods Armadillo: Search your library for a basic land card or a Desert card, reveal it, put it into your hand, then shuffle. You gain 3 life.| +Spinewoods Paladin|Outlaws of Thunder Junction|183|C|{4}{G}|Creature - Human Knight|5|4|Trample$When Spinewoods Paladin enters the battlefield, you gain 3 life.$Plot {3}{G}| +Stubborn Burrowfiend|Outlaws of Thunder Junction|184|U|{1}{G}|Creature - Badger Beast Mount|2|2|Whenever Stubborn Burrowfiend becomes saddled for the first time each turn, mill two cards, then Stubborn Burrowfiend gets +X/+X until end of turn, where X is the number of creature cards in your graveyard.$Saddle 2| +Throw from the Saddle|Outlaws of Thunder Junction|185|C|{1}{G}|Sorcery|||Target creature you control gets +1/+1 until end of turn. Put a +1/+1 counter on it instead if it's a Mount. Then it deals damage equal to its power to target creature you don't control.| +Trash the Town|Outlaws of Thunder Junction|186|U|{G}|Instant|||Spree$+ {2} -- Put two +1/+1 counters on target creature.$+ {1} -- Target creature gains trample until end of turn.$+ {1} -- Until end of turn, target creature gains "Whenever this creature deals combat damage to a player, draw two cards."| +Tumbleweed Rising|Outlaws of Thunder Junction|187|C|{1}{G}|Sorcery|||Create an X/X green Elemental creature token, where X is the greatest power among creatures you control.$Plot {2}{G}| +Voracious Varmint|Outlaws of Thunder Junction|188|C|{1}{G}|Creature - Varmint|2|2|Vigilance${1}, Sacrifice Voracious Varmint: Destroy target artifact or enchantment.| +Akul the Unrepentant|Outlaws of Thunder Junction|189|R|{B}{B}{R}{R}|Legendary Creature - Scorpion Dragon Rogue|5|5|Flying, trample$Sacrifice three other creatures: You may put a creature card from your hand onto the battlefield. Activate only as a sorcery and only once each turn.| +Annie Flash, the Veteran|Outlaws of Thunder Junction|190|M|{3}{R}{G}{W}|Legendary Creature - Human Rogue|4|5|Flash$When Annie Flash, the Veteran enters the battlefield, if you cast it, return target permanent card with mana value 3 or less from your graveyard to the battlefield tapped.$Whenever Annie Flash becomes tapped, exile the top two cards of your library. You may play those cards this turn.| +Annie Joins Up|Outlaws of Thunder Junction|191|R|{1}{R}{G}{W}|Legendary Enchantment|||When Annie Joins Up enters the battlefield, it deals 5 damage to target creature or planeswalker an opponent controls.$If a triggered ability of a legendary creature you control triggers, that ability triggers an additional time.| +Assimilation Aegis|Outlaws of Thunder Junction|192|M|{1}{W}{U}|Artifact - Equipment|||When Assimilation Aegis enters the battlefield, exile up to one target creature until Assimilation Aegis leaves the battlefield.$Whenever Assimilation Aegis becomes attached to a creature, for as long as Assimilation Aegis remains attached to it, that creature becomes a copy of a creature card exiled with Assimilation Aegis.$Equip {2}| +At Knifepoint|Outlaws of Thunder Junction|193|U|{1}{B}{R}|Enchantment|||As long as it's your turn, outlaws you control have first strike.$Whenever you commit a crime, create a 1/1 red Mercenary creature token with "{T}: Target creature you control gets +1/+0 until end of turn. Activate only as a sorcery." This ability triggers only once each turn.| +Badlands Revival|Outlaws of Thunder Junction|194|U|{3}{B}{G}|Sorcery|||Return up to one target creature card from your graveyard to the battlefield. Return up to one target permanent card from your graveyard to your hand.| +Baron Bertram Graywater|Outlaws of Thunder Junction|195|U|{2}{W}{B}|Legendary Creature - Vampire Noble|3|4|Whenever one or more tokens enter the battlefield under your control, create a 1/1 black Vampire Rogue creature token with lifelink. This ability triggers only once each turn.${1}{B}, Sacrifice another creature or artifact: Draw a card.| +Bonny Pall, Clearcutter|Outlaws of Thunder Junction|196|R|{3}{G}{U}{U}|Legendary Creature - Giant Scout|6|5|Reach$When Bonny Pall, Clearcutter enters the battlefield, create Beau, a legendary blue Ox creature token with "This creature's power and toughness are each equal to the number of lands you control."$Whenever you attack, draw a card, then you may put a land card from your hand or graveyard onto the battlefield.| +Breeches, the Blastmaker|Outlaws of Thunder Junction|197|R|{1}{U}{R}|Legendary Creature - Goblin Pirate|3|3|Menace$Whenever you cast your second spell each turn, you may sacrifice an artifact. If you do, flip a coin. When you win the flip, copy that spell. You may choose new targets for the copy. When you lose the flip, Breeches, the Blastmaker deals damage equal to that spell's mana value to any target.| +Bruse Tarl, Roving Rancher|Outlaws of Thunder Junction|198|R|{2}{R}{W}|Legendary Creature - Human Warrior|4|3|Oxen you control have double strike.$Whenever Bruse Tarl, Roving Rancher enters the battlefield or attacks, exile the top card of your library. If it's a land card, create a 2/2 white Ox creature token. Otherwise, you may cast it until the end of your next turn.| +Cactusfolk Sureshot|Outlaws of Thunder Junction|199|U|{2}{R}{G}|Creature - Plant Mercenary|4|4|Reach$Ward {2}$At the beginning of combat on your turn, other creatures you control with power 4 or greater gain trample and haste until end of turn.| +Congregation Gryff|Outlaws of Thunder Junction|200|U|{1}{G}{W}|Creature - Hippogriff Mount|1|4|Flying, lifelink$Whenever Congregation Gryff attacks while saddled, it gets +X/+X until end of turn, where X is the number of Mounts you control.$Saddle 3| +Doc Aurlock, Grizzled Genius|Outlaws of Thunder Junction|201|U|{G}{U}|Legendary Creature - Bear Druid|2|3|Spells you cast from your graveyard or from exile cost {2} less to cast.$Plotting cards from your hand costs {2} less.| +Eriette, the Beguiler|Outlaws of Thunder Junction|202|R|{1}{W}{U}{B}|Legendary Creature - Human Warlock|4|4|Lifelink$Whenever an Aura you control becomes attached to a nonland permanent an opponent controls with mana value less than or equal to that Aura's mana value, gain control of that permanent for as long as that Aura is attached to it.| +Ertha Jo, Frontier Mentor|Outlaws of Thunder Junction|203|U|{2}{R}{W}|Legendary Creature - Kor Advisor|2|4|When Ertha Jo, Frontier Mentor enters the battlefield, create a 1/1 red Mercenary creature token with "{T}: Target creature you control gets +1/+0 until end of turn. Activate only as a sorcery."$Whenever you activate an ability that targets a creature or player, copy that ability. You may choose new targets for the copy.| +Form a Posse|Outlaws of Thunder Junction|204|U|{X}{R}{W}|Sorcery|||Create X 1/1 red Mercenary creature tokens with "{T}: Target creature you control gets +1/+0 until end of turn. Activate only as a sorcery."| +Ghired, Mirror of the Wilds|Outlaws of Thunder Junction|205|M|{R}{G}{W}|Legendary Creature - Human Shaman|3|3|Haste$Nontoken creatures you control have "{T}: Create a token that's a copy of target token you control that entered the battlefield this turn."| +The Gitrog, Ravenous Ride|Outlaws of Thunder Junction|206|M|{3}{B}{G}|Legendary Creature - Frog Horror Mount|6|5|Trample, haste$Whenever The Gitrog, Ravenous Ride deals combat damage to a player, you may sacrifice a creature that saddled it this turn. If you do, draw X cards, then put up to X land cards from your hand onto the battlefield tapped, where X is the sacrificed creature's power.$Saddle 1| +Honest Rutstein|Outlaws of Thunder Junction|207|U|{1}{B}{G}|Legendary Creature - Human Warlock|3|2|When Honest Rutstein enters the battlefield, return target creature card from your graveyard to your hand.$Creature spells you cast cost {1} less to cast.| +Intimidation Campaign|Outlaws of Thunder Junction|208|U|{1}{U}{B}|Enchantment|||When Intimidation Campaign enters the battlefield, each opponent loses 1 life, you gain 1 life, and you draw a card.$Whenever you commit a crime, you may return Intimidation Campaign to its owner's hand.| +Jem Lightfoote, Sky Explorer|Outlaws of Thunder Junction|209|U|{2}{W}{U}|Legendary Creature - Human Scout|3|3|Flying, vigilance$At the beginning of your end step, if you haven't cast a spell from your hand this turn, draw a card.| +Jolene, Plundering Pugilist|Outlaws of Thunder Junction|210|U|{1}{R}{G}|Legendary Creature - Human Mercenary|4|2|Whenever you attack with one or more creatures with power 4 or greater, create a Treasure token.${1}{R}, Sacrifice a Treasure: Jolene, Plundering Pugilist deals 1 damage to any target.| +Kambal, Profiteering Mayor|Outlaws of Thunder Junction|211|R|{1}{W}{B}|Legendary Creature - Human Advisor|2|4|Whenever one or more tokens enter the battlefield under your opponents' control, for each of them, create a tapped token that's a copy of it. This ability triggers only once each turn.$Whenever one or more tokens enter the battlefield under your control, each opponent loses 1 life and you gain 1 life.| +Kellan Joins Up|Outlaws of Thunder Junction|212|R|{G}{W}{U}|Legendary Enchantment|||When Kellan Joins Up enters the battlefield, you may exile a nonland card with mana value 3 or less from your hand. If you do, it becomes plotted.$Whenever a legendary creature enters the battlefield under your control, put a +1/+1 counter on each creature you control.| +Kellan, the Kid|Outlaws of Thunder Junction|213|M|{G}{W}{U}|Legendary Creature - Human Faerie Rogue|3|3|Flying, lifelink$Whenever you cast a spell from anywhere other than your hand, you may cast a permanent spell with equal or lesser mana value from your hand without paying its mana cost. If you don't, you may put a land card from your hand onto the battlefield.| +Kraum, Violent Cacophony|Outlaws of Thunder Junction|214|U|{2}{U}{R}|Legendary Creature - Zombie Horror|2|3|Flying$Whenever you cast your second spell each turn, put a +1/+1 counter on Kraum, Violent Cacophony and draw a card.| +Laughing Jasper Flint|Outlaws of Thunder Junction|215|R|{1}{B}{R}|Legendary Creature - Lizard Rogue|4|3|Creatures you control but don't own are Mercenaries in addition to their other types.$At the beginning of your upkeep, exile the top X cards of target opponent's library, where X is the number of outlaws you control. Until end of turn, you may cast spells from among those cards, and mana of any type can be spent to cast those spells.| +Lazav, Familiar Stranger|Outlaws of Thunder Junction|216|U|{1}{U}{B}|Legendary Creature - Shapeshifter|1|4|Whenever you commit a crime, put a +1/+1 counter on Lazav, Familiar Stranger. Then you may exile a card from a graveyard. If a creature card was exiled this way, you may have Lazav become a copy of that card until end of turn. This ability triggers only once each turn.| +Lilah, Undefeated Slickshot|Outlaws of Thunder Junction|217|R|{1}{U}{R}|Legendary Creature - Human Rogue|3|3|Prowess$Whenever you cast a multicolored instant or sorcery spell from your hand, exile that spell instead of putting it into your graveyard as it resolves. If you do, it becomes plotted.| +Make Your Own Luck|Outlaws of Thunder Junction|218|U|{3}{G}{U}|Sorcery|||Look at the top three cards of your library. You may exile a nonland card from among them. If you do, it becomes plotted. Put the rest into your hand.| +Malcolm, the Eyes|Outlaws of Thunder Junction|219|R|{U}{R}|Legendary Creature - Siren Pirate|2|2|Flying, haste$Whenever you cast your second spell each turn, investigate.| +Marchesa, Dealer of Death|Outlaws of Thunder Junction|220|R|{U}{B}{R}|Legendary Creature - Human Rogue|3|4|Whenever you commit a crime, you may pay {1}. If you do, look at the top two cards of your library. Put one of them into your hand and the other into your graveyard.| +Miriam, Herd Whisperer|Outlaws of Thunder Junction|221|U|{G}{W}|Legendary Creature - Human Druid|3|2|As long as it's your turn, Mounts and Vehicles you control have hexproof.$Whenever a Mount or Vehicle you control attacks, put a +1/+1 counter on it.| +Obeka, Splitter of Seconds|Outlaws of Thunder Junction|222|R|{1}{U}{B}{R}|Legendary Creature - Ogre Warlock|2|5|Menace$Whenever Obeka, Splitter of Seconds deals combat damage to a player, you get that many additional upkeep steps after this phase.| Oko, the Ringleader|Outlaws of Thunder Junction|223|M|{2}{G}{U}|Legendary Planeswalker - Oko|3|At the beginning of combat on your turn, Oko, the Ringleader becomes a copy of up to one target creature you control until end of turn, except he has hexproof.$+1: Draw two cards. If you've committed a crime this turn, discard a card. Otherwise, discard two cards.$-1: Create a 3/3 green Elk creature token.$-5: For each other nonland permanent you control, create a token that's a copy of that permanent.| +Pillage the Bog|Outlaws of Thunder Junction|224|R|{B}{G}|Sorcery|||Look at the top X cards of your library, where X is twice the number of lands you control. Put one of them into your hand and the rest on the bottom of your library in a random order.$Plot {1}{B}{G}| +Rakdos Joins Up|Outlaws of Thunder Junction|225|R|{3}{B}{R}|Legendary Enchantment|||When Rakdos Joins Up enters the battlefield, return target creature card from your graveyard to the battlefield with two additional +1/+1 counters on it.$Whenever a legendary creature you control dies, Rakdos Joins Up deals damage equal to that creature's power to target opponent.| +Rakdos, the Muscle|Outlaws of Thunder Junction|226|M|{2}{B}{B}{R}|Legendary Creature - Demon Mercenary|6|5|Flying, trample$Whenever you sacrifice another creature, exile cards equal to its mana value from the top of target player's library. Until your next end step, you may play those cards, and mana of any type can be spent to cast those spells.$Sacrifice another creature: Rakdos, the Muscle gains indestructible until end of turn. Tap it. Activate only once each turn.| +Riku of Many Paths|Outlaws of Thunder Junction|227|R|{G}{U}{R}|Legendary Creature - Human Wizard|3|3|Whenever you cast a modal spell, choose up to X, where X is the number of times you chose a mode for that spell --$* Exile the top card of your library. Until the end of your next turn, you may play it.$* Put a +1/+1 counter on Riku of Many Paths. It gains trample until end of turn.$* Create a 1/1 blue Bird creature token with flying.| +Roxanne, Starfall Savant|Outlaws of Thunder Junction|228|R|{3}{R}{G}|Legendary Creature - Cat Druid|4|3|Whenever Roxanne, Starfall Savant enters the battlefield or attacks, create a tapped colorless artifact token named Meteorite with "When Meteorite enters the battlefield, it deals 2 damage to any target" and "{T}: Add one mana of any color."$Whenever you tap an artifact token for mana, add one mana of any type that artifact token produced.| +Ruthless Lawbringer|Outlaws of Thunder Junction|229|U|{1}{W}{B}|Creature - Vampire Assassin|3|2|When Ruthless Lawbringer enters the battlefield, you may sacrifice another creature. When you do, destroy target nonland permanent.| +Satoru, the Infiltrator|Outlaws of Thunder Junction|230|R|{U}{B}|Legendary Creature - Human Ninja Rogue|2|3|Menace$Whenever Satoru, the Infiltrator and/or one or more other nontoken creatures enter the battlefield under your control, if none of them were cast or no mana was spent to cast them, draw a card.| +Selvala, Eager Trailblazer|Outlaws of Thunder Junction|231|M|{2}{G}{W}|Legendary Creature - Elf Scout|4|5|Vigilance$Whenever you cast a creature spell, create a 1/1 red Mercenary creature token with "{T}: Target creature you control gets +1/+0 until end of turn. Activate only as a sorcery."${T}: Choose a color. Add one mana of that color for each different power among creatures you control.| +Seraphic Steed|Outlaws of Thunder Junction|232|R|{G}{W}|Creature - Unicorn Mount|2|2|First strike, lifelink$Whenever Seraphic Steed attacks while saddled, create a 3/3 white Angel creature token with flying.$Saddle 4| +Slick Sequence|Outlaws of Thunder Junction|233|U|{U}{R}|Instant|||Slick Sequence deals 2 damage to any target. If you've cast another spell this turn, draw a card.| +Taii Wakeen, Perfect Shot|Outlaws of Thunder Junction|234|R|{R}{W}|Legendary Creature - Human Mercenary|2|3|Whenever a source you control deals noncombat damage to a creature equal to that creature's toughness, draw a card.${X}, {T}: If a source you control would deal noncombat damage to a permanent or player this turn, it deals that much damage plus X instead.| +Vial Smasher, Gleeful Grenadier|Outlaws of Thunder Junction|235|U|{B}{R}|Legendary Creature - Goblin Mercenary|3|2|Whenever another outlaw enters the battlefield under your control, Vial Smasher, Gleeful Grenadier deals 1 damage to target opponent.| +Vraska Joins Up|Outlaws of Thunder Junction|236|R|{B}{G}|Legendary Enchantment|||When Vraska Joins Up enters the battlefield, put a deathtouch counter on each creature you control.$Whenever a legendary creature you control deals combat damage to a player, draw a card.| +Vraska, the Silencer|Outlaws of Thunder Junction|237|M|{1}{B}{G}|Legendary Creature - Gorgon Assassin|3|3|Deathtouch$Whenever a nontoken creature an opponent controls dies, you may pay {1}. If you do, return that card to the battlefield tapped under your control. It's a Treasure artifact with "{T}, Sacrifice this artifact: Add one mana of any color," and it loses all other card types.| +Wrangler of the Damned|Outlaws of Thunder Junction|238|U|{3}{W}{U}|Creature - Human Soldier|1|4|Flash$At the beginning of your end step, if you haven't cast a spell from your hand this turn, create a 2/2 white Spirit creature token with flying.| +Wylie Duke, Atiin Hero|Outlaws of Thunder Junction|239|R|{1}{G}{W}|Legendary Creature - Human Ranger|4|2|Vigilance$Whenever Wylie Duke, Atiin Hero becomes tapped, you gain 1 life and draw a card.| +Bandit's Haul|Outlaws of Thunder Junction|240|U|{3}|Artifact|||Whenever you commit a crime, put a loot counter on Bandit's Haul. This ability triggers only once each turn.${T}: Add one mana of any color.${2}, {T}, Remove two loot counters from Bandit's Haul: Draw a card.| +Boom Box|Outlaws of Thunder Junction|241|U|{2}|Artifact|||{6}, {T}, Sacrifice Boom Box: Destroy up to one target artifact, up to one target creature, and up to one target land.| +Gold Pan|Outlaws of Thunder Junction|242|C|{2}|Artifact - Equipment|||When Gold Pan enters the battlefield, create a Treasure token.$Equipped creature gets +1/+1.$Equip {1}| +Lavaspur Boots|Outlaws of Thunder Junction|243|U|{1}|Artifact - Equipment|||Equipped creature gets +1/+0 and has haste and ward {1}.$Equip {1}| +Luxurious Locomotive|Outlaws of Thunder Junction|244|U|{5}|Artifact - Vehicle|6|5|Whenever Luxurious Locomotive attacks, create a Treasure token for each creature that crewed it this turn.$Crew 1. Activate only once each turn.| +Mobile Homestead|Outlaws of Thunder Junction|245|U|{2}|Artifact - Vehicle|3|3|Mobile Homestead has haste as long as you control a Mount.$Whenever Mobile Homestead attacks, look at the top card of your library. If it's a land card, you may put it onto the battlefield tapped.$Crew 2| +Oasis Gardener|Outlaws of Thunder Junction|246|C|{3}|Artifact Creature - Scarecrow|2|2|When Oasis Gardener enters the battlefield, you gain 2 life.${T}: Add one mana of any color.| +Redrock Sentinel|Outlaws of Thunder Junction|247|U|{3}|Artifact Creature - Golem|2|4|Defender${2}, {T}, Sacrifice a land: Draw a card and create a Treasure token.| +Silver Deputy|Outlaws of Thunder Junction|248|C|{2}|Artifact Creature - Mercenary|1|2|When Silver Deputy enters the battlefield, you may search your library for a basic land card or a Desert card, reveal it, then shuffle and put it on top.${T}: Target creature you control gets +1/+0 until end of turn. Activate only as a sorcery.| +Sterling Hound|Outlaws of Thunder Junction|249|C|{3}|Artifact Creature - Dog|3|2|When Sterling Hound enters the battlefield, surveil 2.| +Tomb Trawler|Outlaws of Thunder Junction|250|U|{2}|Artifact Creature - Golem|0|4|{2}: Put target card from your graveyard on the bottom of your library.| +Abraded Bluffs|Outlaws of Thunder Junction|251|C||Land - Desert|||Abraded Bluffs enters the battlefield tapped.$When Abraded Bluffs enters the battlefield, it deals 1 damage to target opponent.${T}: Add {R} or {W}.| +Arid Archway|Outlaws of Thunder Junction|252|U||Land - Desert|||Arid Archway enters the battlefield tapped.$When Arid Archway enters the battlefield, return a land you control to its owner's hand. If another Desert was returned this way, surveil 1.${T}: Add {C}{C}.| +Bristling Backwoods|Outlaws of Thunder Junction|253|C||Land - Desert|||Bristling Backwoods enters the battlefield tapped.$When Bristling Backwoods enters the battlefield, it deals 1 damage to target opponent.${T}: Add {R} or {G}.| +Conduit Pylons|Outlaws of Thunder Junction|254|C||Land - Desert|||When Conduit Pylons enters the battlefield, surveil 1.${T}: Add {C}.${1}, {T}: Add one mana of any color.| +Creosote Heath|Outlaws of Thunder Junction|255|C||Land - Desert|||Creosote Heath enters the battlefield tapped.$When Creosote Heath enters the battlefield, it deals 1 damage to target opponent.${T}: Add {G} or {W}.| +Eroded Canyon|Outlaws of Thunder Junction|256|C||Land - Desert|||Eroded Canyon enters the battlefield tapped.$When Eroded Canyon enters the battlefield, it deals 1 damage to target opponent.${T}: Add {U} or {R}.| +Festering Gulch|Outlaws of Thunder Junction|257|C||Land - Desert|||Festering Gulch enters the battlefield tapped.$When Festering Gulch enters the battlefield, it deals 1 damage to target opponent.${T}: Add {B} or {G}.| +Forlorn Flats|Outlaws of Thunder Junction|258|C||Land - Desert|||Forlorn Flats enters the battlefield tapped.$When Forlorn Flats enters the battlefield, it deals 1 damage to target opponent.${T}: Add {W} or {B}.| +Jagged Barrens|Outlaws of Thunder Junction|259|C||Land - Desert|||Jagged Barrens enters the battlefield tapped.$When Jagged Barrens enters the battlefield, it deals 1 damage to target opponent.${T}: Add {B} or {R}.| +Lonely Arroyo|Outlaws of Thunder Junction|260|C||Land - Desert|||Lonely Arroyo enters the battlefield tapped.$When Lonely Arroyo enters the battlefield, it deals 1 damage to target opponent.${T}: Add {W} or {U}.| +Lush Oasis|Outlaws of Thunder Junction|261|C||Land - Desert|||Lush Oasis enters the battlefield tapped.$When Lush Oasis enters the battlefield, it deals 1 damage to target opponent.${T}: Add {G} or {U}.| +Mirage Mesa|Outlaws of Thunder Junction|262|C||Land - Desert|||Mirage Mesa enters the battlefield tapped. As it enters, choose a color.${T}: Add one mana of the chosen color.| +Sandstorm Verge|Outlaws of Thunder Junction|263|U||Land - Desert|||{T}: Add {C}.${3}, {T}: Target creature can't block this turn. Activate only as a sorcery.| +Soured Springs|Outlaws of Thunder Junction|264|C||Land - Desert|||Soured Springs enters the battlefield tapped.$When Soured Springs enters the battlefield, it deals 1 damage to target opponent.${T}: Add {U} or {B}.| +Bucolic Ranch|Outlaws of Thunder Junction|265|U||Land - Desert|||{T}: Add {C}.${T}: Add one mana of any color. Spend this mana only to cast a Mount spell.${3}, {T}: Look at the top card of your library. If it's a Mount card, you may reveal it and put it into your hand. If you don't put it into your hand, you may put it on the bottom of your library.| +Blooming Marsh|Outlaws of Thunder Junction|266|R||Land|||Blooming Marsh enters the battlefield tapped unless you control two or fewer other lands.${T}: Add {B} or {G}.| +Botanical Sanctum|Outlaws of Thunder Junction|267|R||Land|||Botanical Sanctum enters the battlefield tapped unless you control two or fewer other lands.${T}: Add {G} or {U}.| +Concealed Courtyard|Outlaws of Thunder Junction|268|R||Land|||Concealed Courtyard enters the battlefield tapped unless you control two or fewer other lands.${T}: Add {W} or {B}.| +Inspiring Vantage|Outlaws of Thunder Junction|269|R||Land|||Inspiring Vantage enters the battlefield tapped unless you control two or fewer other lands.${T}: Add {R} or {W}.| +Spirebluff Canal|Outlaws of Thunder Junction|270|R||Land|||Spirebluff Canal enters the battlefield tapped unless you control two or fewer other lands.${T}: Add {U} or {R}.| +Jace Reawakened|Outlaws of Thunder Junction|271|M|{U}{U}|Legendary Planeswalker - Jace|3|You can't cast this spell during your first, second, or third turns of the game.$+1: Draw a card, then discard a card.$+1: You may exile a nonland card with mana value 3 or less from your hand. If you do, it becomes plotted.$-6: Until end of turn, whenever you cast a spell, copy it. You may choose new targets for the copy.| Plains|Outlaws of Thunder Junction|272|C||Basic Land - Plains|||({T}: Add {W}.)| Island|Outlaws of Thunder Junction|273|C||Basic Land - Island|||({T}: Add {U}.)| Swamp|Outlaws of Thunder Junction|274|C||Basic Land - Swamp|||({T}: Add {B}.)| @@ -52421,8 +52688,36 @@ Swamp|Modern Horizons 3|306|C||Basic Land - Swamp|||({T}: Add {B}.)| Mountain|Modern Horizons 3|307|C||Basic Land - Mountain|||({T}: Add {R}.)| Forest|Modern Horizons 3|308|C||Basic Land - Forest|||({T}: Add {G}.)| Snow-Covered Wastes|Modern Horizons 3|309|U||Basic Snow Land|||{T}: Add {C}.| +Collector's Cage|The Big Score|1|M|{1}{W}|Artifact|||Hideaway 5${1}, {T}: Put a +1/+1 counter on target creature you control. Then if you control three or more creatures with different powers, you may play the exiled card without paying its mana cost.| +Grand Abolisher|The Big Score|2|M|{W}{W}|Creature - Human Cleric|2|2|During your turn, your opponents can't cast spells or activate abilities of artifacts, creatures, or enchantments.| +Oltec Matterweaver|The Big Score|3|M|{2}{W}|Creature - Human Artificer|2|4|Whenever you cast a creature spell, choose one --$* Create a 1/1 colorless Gnome artifact creature token.$* Create a token that's a copy of target artifact token you control.| +Rest in Peace|The Big Score|4|M|{1}{W}|Enchantment|||When Rest in Peace enters the battlefield, exile all graveyards.$If a card or token would be put into a graveyard from anywhere, exile it instead.| +Esoteric Duplicator|The Big Score|5|M|{2}{U}|Artifact - Clue|||Whenever you sacrifice Esoteric Duplicator or another artifact, you may pay {2}. If you do, at the beginning of the next end step, create a token that's a copy of that artifact.${2}, Sacrifice Esoteric Duplicator: Draw a card.| +Simulacrum Synthesizer|The Big Score|6|M|{2}{U}|Artifact|||When Simulacrum Synthesizer enters the battlefield, scry 2.$Whenever another artifact with mana value 3 or greater enters the battlefield under your control, create a 0/0 colorless Construct artifact creature token with "This creature gets +1/+1 for each artifact you control."| +Worldwalker Helm|The Big Score|7|M|{2}{U}|Artifact|||If you would create one or more artifact tokens, instead create those tokens plus an additional Map token.${1}{U}, {T}: Create a token that's a copy of target artifact token you control.| +Greed's Gambit|The Big Score|8|M|{3}{B}|Enchantment|||When Greed's Gambit enters the battlefield, you draw three cards, gain 6 life, and create three 2/1 black Bat creature tokens with flying.$At the beginning of your end step, you discard a card, lose 2 life, and sacrifice a creature.$When Greed's Gambit leaves the battlefield, you discard three cards, lose 6 life, and sacrifice three creatures.| +Harvester of Misery|The Big Score|9|M|{3}{B}{B}|Creature - Spirit|5|4|Menace$When Harvester of Misery enters the battlefield, other creatures get -2/-2 until end of turn.${1}{B}, Discard Harvester of Misery: Target creature gets -2/-2 until end of turn.| +Hostile Investigator|The Big Score|10|M|{3}{B}|Creature - Ogre Rogue Detective|4|3|When Hostile Investigator enters the battlefield, target opponent discards a card.$Whenever one or more players discard one or more cards, investigate. This ability triggers only once each turn.| +Generous Plunderer|The Big Score|11|M|{1}{R}|Creature - Human Rogue|2|2|Menace$At the beginning of your upkeep, you may create a Treasure token. When you do, target opponent creates a tapped Treasure token.$Whenever Generous Plunderer attacks, it deals damage to defending player equal to the number of artifacts they control.| +Legion Extruder|The Big Score|12|M|{1}{R}|Artifact|||When Legion Extruder enters the battlefield, it deals 2 damage to any target.${2}, {T}, Sacrifice another artifact: Create a 3/3 colorless Golem artifact creature token.| +Memory Vessel|The Big Score|13|M|{3}{R}{R}|Artifact|||{T}, Exile Memory Vessel: Each player exiles the top seven cards of their library. Until your next turn, players may play cards they exiled this way, and they can't play cards from their hand. Activate only as a sorcery.| +Molten Duplication|The Big Score|14|M|{1}{R}|Sorcery|||Create a token that's a copy of target artifact or creature you control, except it's an artifact in addition to its other types. It gains haste until end of turn. Sacrifice it at the beginning of the next end step.| +Territory Forge|The Big Score|15|M|{4}{R}|Artifact|||When Territory Forge enters the battlefield, if you cast it, exile target artifact or land.$Territory Forge has all activated abilities of the exiled card.| +Ancient Cornucopia|The Big Score|16|M|{2}{G}|Artifact|||Whenever you cast a spell that's one or more colors, you may gain 1 life for each of that spell's colors. Do this only once each turn.${T}: Add one mana of any color.| +Bristlebud Farmer|The Big Score|17|M|{2}{G}{G}|Creature - Plant Druid|5|5|Trample$When Bristlebud Farmer enters the battlefield, create two Food tokens.$Whenever Bristlebud Farmer attacks, you may sacrifice a Food. If you do, mill three cards. You may put a permanent card from among them into your hand.| +Omenpath Journey|The Big Score|18|M|{3}{G}|Enchantment|||When Omenpath Journey enters the battlefield, search your library for up to five land cards that have different names, exile them, then shuffle.$At the beginning of your end step, choose a card at random exiled with Omenpath Journey and put it onto the battlefield tapped.| +Sandstorm Salvager|The Big Score|19|M|{2}{G}|Creature - Human Artificer|1|1|When Sandstorm Salvager enters the battlefield, create a 3/3 colorless Golem artifact creature token.${2}, {T}: Put a +1/+1 counter on each creature token you control. They gain trample until end of turn.| +Vaultborn Tyrant|The Big Score|20|M|{5}{G}{G}|Creature - Dinosaur|6|6|Trample$Whenever Vaultborn Tyrant or another creature with power 4 or greater enters the battlefield under your control, you gain 3 life and draw a card.$When Vaultborn Tyrant dies, if it's not a token, create a token that's a copy of it, except it's an artifact in addition to its other types.| +Loot, the Key to Everything|The Big Score|21|M|{G}{U}{R}|Legendary Creature - Beast Noble|1|2|Ward {1}$At the beginning of your upkeep, exile the top X cards of your library, where X is the number of card types among other nonland permanents you control. You may play those cards this turn.| +Pest Control|The Big Score|22|M|{W}{B}|Sorcery|||Destroy all nonland permanents with mana value 1 or less.$Cycling {2}| +Lost Jitte|The Big Score|23|M|{1}|Legendary Artifact - Equipment|||Whenever equipped creature deals combat damage, put a charge counter on Lost Jitte.$Remove a charge counter from Lost Jitte: Choose one --$* Untap target land.$* Target creature can't block this turn.$* Put a +1/+1 counter on equipped creature.$Equip {1}| +Lotus Ring|The Big Score|24|M|{3}|Artifact - Equipment|||Indestructible$Equipped creature gets +3/+3 and has vigilance and "{T}, Sacrifice this creature: Add three mana of any one color."$Equip {3}| Nexus of Becoming|The Big Score|25|M|{6}|Artifact|||At the beginning of combat on your turn, draw a card. Then you may exile an artifact or creature card from your hand. If you do, create a token that's a copy of the exiled card, except it's a 3/3 Golem artifact creature in addition to its other types.| -Sword of Wealth and Power|The Big Score|26|M|{3}|Artifact- Equipment|||Equipped creature gets +2/+2 and has protection from instants and from sorceries.$Whenever equipped creature deals combat damage to a player, create a Treasure token. When you next cast an instant or sorcery spell this turn, copy that spell. You may choose new targets for the copy.$Equip {2}| +Sword of Wealth and Power|The Big Score|26|M|{3}|Artifact - Equipment|||Equipped creature gets +2/+2 and has protection from instants and from sorceries.$Whenever equipped creature deals combat damage to a player, create a Treasure token. When you next cast an instant or sorcery spell this turn, copy that spell. You may choose new targets for the copy.$Equip {2}| +Torpor Orb|The Big Score|27|M|{2}|Artifact|||Creatures entering the battlefield don't cause abilities to trigger.| +Transmutation Font|The Big Score|28|M|{5}|Artifact|||{T}: Create your choice of a Blood token, a Clue token, or a Food token.${3}, {T}, Sacrifice three artifact tokens with different names: Search your library for an artifact card, put it onto the battlefield, then shuffle. Activate only as a sorcery.| +Fomori Vault|The Big Score|29|M||Land|||{T}: Add {1}.${3}, {T}, Discard a card: Look at the top X cards of your library, where X is the number of artifacts you control. Put one of them into your hand and the rest on the bottom of your library in random order.| +Tarnation Vista|The Big Score|30|M||Land|||Tarnation Vista enters the battlefield tapped. As it enters, choose a color.${T}: Add one mana of the chosen color.${1}, {T}: For each color among monocolored permanents you control, add one mana of that color.| Lumra, Bellow of the Woods|Bloomburrow|183|M|{4}{G}{G}|Legendary Creature - Elemental Bear|*|*|Vigilance, reach$Lumra, Bellow of the Woods's power and toughness are each equal to the number of lands you control.$When Lumra enters, mill four cards. Then return all land cards from your graveyard to the battlefield tapped.| Mabel, Heir to Cragflame|Bloomburrow|224|R|{1}{R}{W}|Legendary Creature - Mouse Soldier|3|3|Other Mice you control get +1/+1.$When Mabel, Heir to Cragflame enters, create Cragflame, a legendary colorless Equipment artifact token with "Equipped creature gets +1/+1 and has vigilance, trample, and haste" and equip {2}.| Mountain|Bloomburrow|274|C||Basic Land - Mountain|||({T}: Add {R}.)| @@ -52550,3 +52845,43 @@ Witch's Familiar|Arena Beginner Set|66|C|{2}{B}|Creature - Frog|2|3|| Woodland Mystic|Arena Beginner Set|109|C|{1}{G}|Creature - Elf Druid|1|1|{T}: Add {G}.| World Shaper|Arena Beginner Set|110|R|{3}{G}|Creature - Merfolk Shaman|3|3|Whenever World Shaper attacks, you may mill three cards.$When World Shaper dies, return all land cards from your graveyard to the battlefield tapped.| Zephyr Gull|Arena Beginner Set|44|C|{U}|Creature - Bird|1|1|| +Gonti, Canny Acquisitor|Outlaws of Thunder Junction Commander|1|M|{2}{B}{G}{U}|Legendary Creature - Aetherborn Rogue|5|5|Spells you cast but don't own cost {1} less to cast.$Whenever one or more creatures you control deal combat damage to a player, look at the top card of that player's library, then exile it face down. You may play that card for as long as it remains exiled, and mana of any type can be spent to cast that spell.| +Olivia, Opulent Outlaw|Outlaws of Thunder Junction Commander|2|M|{1}{R}{W}{B}|Legendary Creature - Vampire Assassin|3|3|Flying, lifelink$Whenever one or more outlaws you control deal combat damage to a player, create a Treasure token.${3}, Sacrifice two Treasures: Put two +1/+1 counters on each creature you control. Activate only as a sorcery.| +Stella Lee, Wild Card|Outlaws of Thunder Junction Commander|3|M|{1}{U}{R}|Legendary Creature - Human Rogue|2|4|Whenever you cast your second spell each turn, exile the top card of your library. Until the end of your next turn, you may play that card.${T}: Copy target instant or sorcery spell you control. You may choose new targets for the copy. Activate only if you've cast three or more spells this turn.| +Yuma, Proud Protector|Outlaws of Thunder Junction Commander|4|M|{5}{R}{G}{W}|Legendary Creature - Human Ranger|6|6|This spell costs {1} less to cast for each land card in your graveyard.$Whenever Yuma, Proud Protector enters the battlefield or attacks, you may sacrifice a land. If you do, draw a card.$Whenever a Desert card is put into your graveyard from anywhere, create a 4/2 green Plant Warrior creature token with reach.| +Eris, Roar of the Storm|Outlaws of Thunder Junction Commander|5|M|{8}{U}{R}|Legendary Creature - Elemental Warlock|4|4|This spell costs {2} less to cast for each different mana value among instant and sorcery cards in your graveyard.$Flying, prowess$Whenever you cast your second spell each turn, create a 4/4 red Dragon Elemental creature token with flying and prowess.| +Felix Five-Boots|Outlaws of Thunder Junction Commander|6|M|{2}{B}{G}{U}|Legendary Creature - Ooze Rogue|5|4|Menace, ward {2}$If a creature you control dealing combat damage to a player causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time.| +Kirri, Talented Sprout|Outlaws of Thunder Junction Commander|7|M|{1}{R}{G}{W}|Legendary Creature - Plant Druid|0|3|Other Plants and Treefolk you control get +2/+0.$At the beginning of your postcombat main phase, return target Plant, Treefolk, or land card from your graveyard to your hand.| +Vihaan, Goldwaker|Outlaws of Thunder Junction Commander|8|M|{R}{W}{B}|Legendary Creature - Dwarf Warlock|3|3|Other outlaws you control have vigilance and haste.$At the beginning of combat on your turn, you may have Treasures you control become 3/3 Construct Assassin artifact creatures in addition to their other types until end of turn.| +Angel of Indemnity|Outlaws of Thunder Junction Commander|9|R|{5}{W}|Creature - Angel Warrior|5|5|Flying, lifelink$When Angel of Indemnity enters the battlefield, return target permanent card with mana value 4 or less from your graveyard to the battlefield.$Encore {6}{W}{W}| +Angelic Sell-Sword|Outlaws of Thunder Junction Commander|10|R|{4}{W}|Creature - Angel Mercenary|4|4|Flying, vigilance$Whenever Angelic Sell-Sword or another nontoken creature enters the battlefield under your control, create a 1/1 red Mercenary creature token with "{T}: Target creature you control gets +1/+0 until end of turn. Activate only as a sorcery."$Whenever Angelic Sell-Sword attacks, if its power is 6 or greater, draw a card.| +Sand Scout|Outlaws of Thunder Junction Commander|11|R|{1}{W}|Creature - Human Scout|2|2|When Sand Scout enters the battlefield, if an opponent controls more lands than you, search your library for a Desert card, put it onto the battlefield tapped, then shuffle.$Whenever one or more land cards are put into your graveyard from anywhere, create a 1/1 red, green, and white Sand Warrior creature token. This ability triggers only once each turn.| +We Ride at Dawn|Outlaws of Thunder Junction Commander|12|R|{2}{W}|Enchantment|||Legendary creature spells you cast have convoke.$Whenever your commander attacks, create a 1/1 red Mercenary creature token with "{T}: Target creature you control gets +1/+0 until end of turn. Activate only as a sorcery."| +Arcane Heist|Outlaws of Thunder Junction Commander|13|R|{2}{U}{U}|Sorcery|||You may cast target instant or sorcery card from an opponent's graveyard without paying its mana cost. If that spell would be put into their graveyard, exile it instead.$Cipher| +Forger's Foundry|Outlaws of Thunder Junction Commander|14|R|{2}{U}|Artifact|||{T}: Add {U}. When you spend this mana to cast an instant or sorcery spell with mana value 3 or less, you may exile that spell instead of putting it into its owner's graveyard as it resolves.${3}{U}{U}, {T}: You may cast any number of spells from among cards exiled with Forger's Foundry without paying their mana costs. Activate only as a sorcery.| +Lock and Load|Outlaws of Thunder Junction Commander|15|R|{2}{U}|Sorcery|||Draw a card, then draw a card for each other instant and sorcery spell you've cast this turn.$Plot {3}{U}| +Smirking Spelljacker|Outlaws of Thunder Junction Commander|16|R|{4}{U}|Creature - Djinn Wizard Rogue|3|3|Flash$Flying$When Smirking Spelljacker enters the battlefield, exile target spell an opponent controls.$Whenever Smirking Spelljacker attacks, if a card is exiled with it, you may cast the exiled card without paying its mana cost.| +Thunderclap Drake|Outlaws of Thunder Junction Commander|17|R|{1}{U}|Creature - Drake|2|1|Flying$Instant and sorcery spells you cast cost {1} less to cast.${2}{U}, Sacrifice Thunderclap Drake: When you cast your next instant or sorcery spell this turn, copy it for each time you've cast your commander from the command zone this game. You may choose new targets for the copies.| +Back in Town|Outlaws of Thunder Junction Commander|18|R|{X}{2}{B}|Sorcery|||Return X target outlaw creature cards from your graveyard to the battlefield.| +Charred Graverobber|Outlaws of Thunder Junction Commander|19|R|{2}{B}|Creature - Skeleton Mercenary|3|1|When Charred Graverobber enters the battlefield, return target outlaw card from your graveyard to your hand.$Escape---{3}{B}, Exile four other cards from your graveyard. Charred Graverobber escapes with a +1/+1 counter on it.| +Discreet Retreat|Outlaws of Thunder Junction Commander|20|R|{3}{B}|Enchantment - Aura|||Enchanted land has {T}: Add two mana of any one color. Spend this mana only to cast outlaw spells or activate abilities from outlaw sources."$Whenever you cast your first outlaw spell each turn, you draw a card and you lose 1 life.| +Heartless Conscription|Outlaws of Thunder Junction Commander|21|R|{6}{B}{B}|Sorcery|||Exile all creatures. For each card exiled this way, you may play that card for as long as it remains exiled, and mana of any type can be spent to cast that spell. Exile Heartless Conscription.| +Orochi Soul-Reaver|Outlaws of Thunder Junction Commander|22|R|{5}{B}|Creature - Snake Ninja Rogue|5|4|Ninjutsu {3}{B}$Whenever one or more creatures you control deal combat damage to a player, create a Treasure token and manifest the top card of that player's library.| +Thieving Varmint|Outlaws of Thunder Junction Commander|23|R|{1}{B}|Creature - Varmint|2|1|Deathtouch, lifelink${T}, Pay 1 life: Add two mana of any one color. Spend this mana only to cast spells you don't own.| +Cataclysmic Prospecting|Outlaws of Thunder Junction Commander|24|R|{X}{R}{R}|Sorcery|||Cataclysmic Prospecting deals X damage to each creature. For each mana from a Desert spent to cast this spell, create a tapped Treasure token.| +Crackling Spellslinger|Outlaws of Thunder Junction Commander|25|R|{3}{R}{R}|Creature - Human Wizard|2|2|Flash$When Crackling Spellslinger enters the battlefield, if you cast it, the next instant or sorcery spell you cast this turn has storm.| +Dead Before Sunrise|Outlaws of Thunder Junction Commander|26|R|{3}{R}|Instant|||Until end of turn, outlaw creatures you control get +1/+0 and gain "{T}: This creature deals damage equal to its power to target creature."| +Elemental Eruption|Outlaws of Thunder Junction Commander|27|R|{4}{R}{R}|Sorcery|||Create a 4/4 red Dragon Elemental creature token with flying and prowess.$Storm| +Embrace the Unknown|Outlaws of Thunder Junction Commander|28|R|{2}{R}|Sorcery|||Exile the top two cards of your library. Until the end of your next turn, you may play those cards.$Retrace| +Pyretic Charge|Outlaws of Thunder Junction Commander|29|R|{4}{R}|Sorcery|||Discard your hand, then draw four cards. For each card discarded this way, creatures you control get +1/+0 until end of turn.$Plot {3}{R}| +Smoldering Stagecoach|Outlaws of Thunder Junction Commander|30|R|{3}{R}|Artifact - Vehicle|*|5|Smoldering Stagecoach's power is equal to the number of instant and sorcery cards in your graveyard.$Whenever Smoldering Stagecoach attacks, the next instant spell and the next sorcery spell you cast this turn each have cascade.$Crew 2| +Dune Chanter|Outlaws of Thunder Junction Commander|31|R|{2}{G}|Creature - Plant Druid|2|3|Reach$Lands you control and land cards you own that aren't on the battlefield are Deserts in addition to their other types.$Lands you control have "{T}: Add one mana of any color."${T}: Mill two cards. You gain 1 life for each land card milled this way.| +Rumbleweed|Outlaws of Thunder Junction Commander|32|R|{10}{G}|Creature - Plant Elemental|8|8|This spell costs {1} less to cast for each land card in your graveyard.$Vigilance, reach, trample$When Rumbleweed enters the battlefield, other creatures you control get +3/+3 and gain trample until end of turn.| +Savvy Trader|Outlaws of Thunder Junction Commander|33|R|{3}{G}|Creature Human Citizen|3|3|When Savvy Trader enters the battlefield, exile target permanent card from your graveyard. You may play that card for as long as it remains exiled.$Spells you cast from anywhere other than your hand cost {1} less to cast.| +Tower Winder|Outlaws of Thunder Junction Commander|34|R|{1}{G}|Creature - Snake|1|1|Reach, deathtouch$When Tower Winder enters the battlefield, search your library and/or graveyard for a card named Command Tower, reveal it, and put it into your hand. If you search your library this way, shuffle.| +Vengeful Regrowth|Outlaws of Thunder Junction Commander|35|R|{4}{G}{G}|Sorcery|||Return up to three target land cards from your graveyard to the battlefield tapped. Create that many 4/2 green Plant Warrior creature tokens with reach.$Flashback {6}{G}{G}| +Graywater's Fixer|Outlaws of Thunder Junction Commander|36|R|{2}{B}{R}|Creature - Lizard Mercenary|4|4|Each outlaw creature card in your graveyard has encore {X}, where X is its mana value.| +Bounty Board|Outlaws of Thunder Junction Commander|37|R|{3}|Artifact|||{T}: Add one mana of any color.${1}, {T}: Put a bounty counter on target creature. Activate only as a sorcery.$Whenever a creature with a bounty counter on it dies, each of its controller's opponents draws a card and gains 2 life.| +Dream-Thief's Bandana|Outlaws of Thunder Junction Commander|38|R|{2}|Artifact - Equipment|||Whenever equipped creature deals combat damage to a player, look at the top card of their library, then exile it face down. For as long as it remains exiled, you may play it, and mana of any type can be spent to cast that spell.$Equip {1}| +Leyline Dowser|Outlaws of Thunder Junction Commander|39|R|{2}|Artifact|||{1}, {T}: Mill a card. You may put an instant or sorcery card milled this way into your hand. Tap an untapped legendary creature you control: Untap Leyline Dowser.| +Cactus Preserve|Outlaws of Thunder Junction Commander|40|R||Land - Desert|||Cactus Preserve enters the battlefield tapped.${T}: Add one mana of any type that a land you control could produce.${3}: Until end of turn, Cactus Preserve becomes an X/X green Plant creature with reach, where X is the greatest mana value among your commanders. It's still a land.| diff --git a/Utils/mtg-sets-data.txt b/Utils/mtg-sets-data.txt index 2f9c7e8e702..a25a91f12ff 100644 --- a/Utils/mtg-sets-data.txt +++ b/Utils/mtg-sets-data.txt @@ -197,6 +197,7 @@ Odyssey|ODY| Oath of the Gatewatch|OGW| Onslaught|ONS| Outlaws of Thunder Junction|OTJ| +Outlaws of Thunder Junction Commander|OTC| The Big Score|BIG| Magic Origins|ORI| Phyrexia: All Will Be One|ONE|